@calo-design/cli 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1678 @@
1
+ import React from "react";
2
+ import {
3
+ CaloBadge,
4
+ CaloBanner,
5
+ CaloBottomSheet,
6
+ CaloBottomNavigation,
7
+ CaloBottomActionBar,
8
+ CaloButton,
9
+ CaloCardDetailsInput,
10
+ CaloCartItemCard,
11
+ CaloCafeProductCard,
12
+ CaloCategoryTileGrid,
13
+ CaloCarousel,
14
+ CaloCenteredTitleTopBar,
15
+ CaloCheckbox,
16
+ CaloCompactProductRowCard,
17
+ CaloCompactVariantTile,
18
+ CaloContextScreen,
19
+ CaloContextTopBar,
20
+ CaloCustomizationOptionCard,
21
+ CaloDateBar,
22
+ CaloDateBarItem,
23
+ CaloDeliveryStatusCard,
24
+ CaloDietaryCard,
25
+ CaloDivider,
26
+ CaloFoodPreferenceWarning,
27
+ CaloIcon,
28
+ CaloIconButton,
29
+ CaloInput,
30
+ CaloInline,
31
+ CaloLogo,
32
+ CaloMacroSummary,
33
+ CaloMarketProductCard,
34
+ CaloMealRatingCard,
35
+ CaloMealRow,
36
+ CaloMenuMealRow,
37
+ CaloPaymentMethodList,
38
+ CaloPhoneInput,
39
+ CaloPriceText,
40
+ CaloProductTile,
41
+ CaloPromoCodeInput,
42
+ CaloRadio,
43
+ CaloRatingChip,
44
+ CaloScreen,
45
+ CaloSelectableMealTile,
46
+ CaloSelectionCard,
47
+ CaloSelectionCardMacros,
48
+ CaloSelectionCardPrice,
49
+ CaloSelectionCardRadioGroup,
50
+ CaloSelectionCardRadioItem,
51
+ CaloStack,
52
+ CaloStepper,
53
+ CaloInlineHeader,
54
+ CaloLargeTitleHeader,
55
+ CaloSummaryHeader,
56
+ CaloSwapMealCard,
57
+ CaloSurface,
58
+ CaloTabMenuGroup,
59
+ CaloText,
60
+ CaloTextArea,
61
+ CaloToast,
62
+ CaloToggle,
63
+ CaloTopActionBar,
64
+ CaloVerificationCodeInput,
65
+ CaloVisual,
66
+ CaloVoucherCard,
67
+ caloVisualAssets
68
+ } from "@calo/design-system";
69
+ import {
70
+ CaloFaqAccordion,
71
+ CaloTestimonialCard,
72
+ CaloSupportPromptCard,
73
+ CaloUgcCard,
74
+ CaloFeatureCard,
75
+ CaloPartnerCard,
76
+ CaloPlanCard,
77
+ CaloPlanCardGrid,
78
+ CaloQuantityRow,
79
+ CaloPagination,
80
+ CaloMealMacroCard,
81
+ CaloDishCard,
82
+ CaloCheckoutSummaryCard
83
+ } from "@calo/design-system";
84
+ import { router } from "expo-router";
85
+
86
+ const mealImage = require("../assets/component-gallery/meal-photo.png");
87
+ const smoothieImage = require("../assets/component-gallery/smoothie-photo.png");
88
+ const ingredientPenneImage = require("../assets/live-flows/active-subscriber-home/ingredient-penne.png");
89
+ const marketImage = caloVisualAssets["salad-bowl-1"].source;
90
+ const sauceImage = caloVisualAssets["caramel"].source;
91
+
92
+ const cafeCategoryItems = [
93
+ { id: "popular", label: "Popular", visual: "fire" },
94
+ { id: "bone-broth", label: "Bone Broth", visual: "bone-broth" },
95
+ { id: "cold-drinks", label: "Cold Drinks", visual: "cold-drinks-l" },
96
+ { id: "froyo", label: "Froyo", visual: "froyo-l" },
97
+ { id: "hot-drinks", label: "Hot Drinks", visual: "hot-drinks-l" },
98
+ { id: "iced-tea", label: "Iced Tea", visual: "iced-tea-l" },
99
+ { id: "meals", label: "Meals", visual: "meal-grid" },
100
+ { id: "salads", label: "Salads", visual: "salads" },
101
+ { id: "sandwiches", label: "Sandwiches", visual: "sandwich" },
102
+ { id: "smoothies", label: "Smoothies", visual: "smoothie-1" },
103
+ { id: "snacks", label: "Snacks", visual: "savory-snacks" },
104
+ { id: "soups", label: "Soups", visual: "soups" },
105
+ { id: "juices", label: "Juices", visual: "beverages" },
106
+ { id: "sweet-snacks", label: "Snacks", visual: "sweet-tooth" }
107
+ ] as const;
108
+
109
+ const macros = [
110
+ { label: "P", value: "37g", tone: "protein" as const },
111
+ { label: "C", value: "42g", tone: "carbs" as const },
112
+ { label: "F", value: "14g", tone: "fat" as const }
113
+ ];
114
+
115
+ export type GalleryCategory = "all" | "foundations" | "navigation" | "actions" | "selection" | "forms" | "commerce" | "food" | "feedback" | "marketing";
116
+
117
+ export const galleryFilters: Array<{ id: GalleryCategory; label: string }> = [
118
+ { id: "all", label: "All" },
119
+ { id: "foundations", label: "Foundations" },
120
+ { id: "navigation", label: "Navigation" },
121
+ { id: "actions", label: "Actions" },
122
+ { id: "selection", label: "Selection" },
123
+ { id: "forms", label: "Forms" },
124
+ { id: "commerce", label: "Commerce" },
125
+ { id: "food", label: "Food" },
126
+ { id: "feedback", label: "Feedback" },
127
+ { id: "marketing", label: "Marketing" }
128
+ ];
129
+
130
+ const gallerySections: Array<{ id: string; category: Exclude<GalleryCategory, "all"> }> = [
131
+ { id: "identity", category: "foundations" },
132
+ { id: "typography", category: "foundations" },
133
+ { id: "carousel", category: "foundations" },
134
+ { id: "bottom-navigation", category: "navigation" },
135
+ { id: "actions", category: "actions" },
136
+ { id: "badges", category: "actions" },
137
+ { id: "selection", category: "selection" },
138
+ { id: "quantity", category: "selection" },
139
+ { id: "inputs", category: "forms" },
140
+ { id: "feedback", category: "feedback" },
141
+ { id: "offers", category: "commerce" },
142
+ { id: "delivery-status", category: "commerce" },
143
+ { id: "payment", category: "commerce" },
144
+ { id: "dietary-cards", category: "food" },
145
+ { id: "category-tile-grid", category: "food" },
146
+ { id: "meal-cards", category: "food" },
147
+ { id: "cart-items", category: "commerce" },
148
+ { id: "faq", category: "marketing" },
149
+ { id: "marketing-cards", category: "marketing" },
150
+ { id: "partner-cards", category: "marketing" },
151
+ { id: "plan-cards", category: "food" },
152
+ { id: "quantity-row", category: "selection" },
153
+ { id: "pagination", category: "navigation" },
154
+ { id: "meal-feature-cards", category: "food" },
155
+ { id: "checkout-summary", category: "commerce" }
156
+ ];
157
+
158
+ const tabMenuOptions = [
159
+ { id: "options", label: "Options" },
160
+ { id: "details", label: "Options" },
161
+ { id: "delivery", label: "Options" },
162
+ { id: "billing", label: "Options" }
163
+ ];
164
+
165
+ const chipMenuOptions = [
166
+ { id: "menu", label: "Menu" },
167
+ { id: "plan", label: "Menu" },
168
+ { id: "snacks", label: "Menu" },
169
+ { id: "drinks", label: "Menu" }
170
+ ];
171
+
172
+ export function getVisibleGallerySectionCount(selectedFilter: GalleryCategory) {
173
+ return gallerySections.filter((section) => selectedFilter === "all" || selectedFilter === section.category).length;
174
+ }
175
+
176
+ export default function ComponentsScreen() {
177
+ return (
178
+ <CaloScreen title="Component Gallery" subtitle="A living Expo preview for Calo design-system components." contentWidth="wide" gutter="wide">
179
+ <ComponentGalleryContent />
180
+ </CaloScreen>
181
+ );
182
+ }
183
+
184
+ export function ComponentGalleryContent({
185
+ showIntro = true,
186
+ selectedFilter: controlledSelectedFilter,
187
+ onFilterChange
188
+ }: {
189
+ showIntro?: boolean;
190
+ selectedFilter?: GalleryCategory;
191
+ onFilterChange?: (filter: GalleryCategory) => void;
192
+ }) {
193
+ const [localSelectedFilter, setLocalSelectedFilter] = React.useState<GalleryCategory>("all");
194
+ const selectedFilter = controlledSelectedFilter ?? localSelectedFilter;
195
+ const setSelectedFilter = onFilterChange ?? setLocalSelectedFilter;
196
+ const [stepperValue, setStepperValue] = React.useState(2);
197
+ const [webMeals, setWebMeals] = React.useState(3);
198
+ const [paginationPage, setPaginationPage] = React.useState(4);
199
+ const [actionBarQuantity, setActionBarQuantity] = React.useState(5);
200
+ const [bottomNavigationTab, setBottomNavigationTab] = React.useState("meals");
201
+ const [compactValue, setCompactValue] = React.useState(0);
202
+ const [toggleSelected, setToggleSelected] = React.useState(true);
203
+ const [selectedDate, setSelectedDate] = React.useState("may-25");
204
+ const [outlineTab, setOutlineTab] = React.useState("options");
205
+ const [chipTab, setChipTab] = React.useState("menu");
206
+ const [bannerSelected, setBannerSelected] = React.useState(false);
207
+ const [modalSheetOpen, setModalSheetOpen] = React.useState(() => {
208
+ if (typeof window === "undefined") return false;
209
+ return new URLSearchParams(window.location.search).get("sheet") === "open";
210
+ });
211
+ const [radioSelected, setRadioSelected] = React.useState("regular");
212
+ const [selectionCardOption, setSelectionCardOption] = React.useState("weekly");
213
+ const [selectionCardChoice, setSelectionCardChoice] = React.useState("value");
214
+ const [checkboxSelected, setCheckboxSelected] = React.useState(true);
215
+ const [rating, setRating] = React.useState("Loved it");
216
+ const [rowRating, setRowRating] = React.useState("okay");
217
+ const [cardRating, setCardRating] = React.useState("awesome");
218
+ const [mealSelected, setMealSelected] = React.useState(true);
219
+ const [selectedVariant, setSelectedVariant] = React.useState("rose");
220
+ const [dietaryPlan, setDietaryPlan] = React.useState("balanced");
221
+ const [marketQuantity, setMarketQuantity] = React.useState(0);
222
+ const [cafeQuantity, setCafeQuantity] = React.useState(1);
223
+ const [paymentMethodId, setPaymentMethodId] = React.useState("wallet");
224
+ const [fieldValue, setFieldValue] = React.useState("Sufiyan Yusuf");
225
+ const [noteValue, setNoteValue] = React.useState("No onions, extra lemon.");
226
+ const [codeValue, setCodeValue] = React.useState("24");
227
+ const [phoneValue, setPhoneValue] = React.useState("501234567");
228
+ const [promoValue, setPromoValue] = React.useState("SAVE20");
229
+ const [cardNumber, setCardNumber] = React.useState("4242 4242 4242 4242");
230
+ const [expiry, setExpiry] = React.useState("04/28");
231
+ const [cvv, setCvv] = React.useState("123");
232
+ const [healthySansAvailable, setHealthySansAvailable] = React.useState(false);
233
+ const previewOnly = () => undefined;
234
+ const filterVisible = (category: GalleryCategory) => selectedFilter === "all" || selectedFilter === category;
235
+ const visibleSectionCount = getVisibleGallerySectionCount(selectedFilter);
236
+
237
+ React.useEffect(() => {
238
+ if (typeof window === "undefined") return;
239
+ if (new URLSearchParams(window.location.search).get("sheet") === "open") {
240
+ setModalSheetOpen(true);
241
+ }
242
+ }, []);
243
+
244
+ React.useEffect(() => {
245
+ setHealthySansAvailable(isHealthySansAvailable());
246
+ }, []);
247
+
248
+ return (
249
+ <CaloStack gap="size40">
250
+ {showIntro ? (
251
+ <>
252
+ <CaloSurface padding="size16" elevation="raised">
253
+ <CaloStack gap="size12">
254
+ <CaloInline align="center" justify="between" wrap>
255
+ <CaloInline align="center" gap="size10">
256
+ <CaloLogo variant="mark" size="sm" />
257
+ <CaloBadge label="Expo gallery" variant="brand" />
258
+ </CaloInline>
259
+ <CaloButton label="Back home" variant="tertiary" size="small" onPress={() => router.push("/")} />
260
+ </CaloInline>
261
+ <CaloText role="bodyMuted">
262
+ This is not Storybook yet. It is a runnable component showroom inside the same prototype app agents use to build Calo UI.
263
+ </CaloText>
264
+ </CaloStack>
265
+ </CaloSurface>
266
+
267
+ <ComponentGalleryFilters selectedFilter={selectedFilter} visibleSectionCount={visibleSectionCount} onFilterChange={setSelectedFilter} />
268
+ </>
269
+ ) : null}
270
+
271
+ {filterVisible("foundations") ? (
272
+ <GallerySection title="Identity" label="Foundations">
273
+ <CaloInline align="center" gap="size16" wrap>
274
+ <CaloLogo variant="wordmark" size="md" />
275
+ <CaloLogo variant="mark" size="md" />
276
+ <CaloVisual name="green-gift" size="lg" />
277
+ <CaloVisual name="calo-truck" size="lg" />
278
+ <CaloVisual name="club-wallet" size="lg" />
279
+ </CaloInline>
280
+ </GallerySection>
281
+ ) : null}
282
+
283
+ {filterVisible("foundations") ? (
284
+ <GallerySection title="Typography" label="Foundations">
285
+ <VariantGroup title="Screen hierarchy">
286
+ <CaloStack gap="size10">
287
+ <TypographySpecimen roleName="display" role="display" sample="Aa" usage="High-impact moments only" />
288
+ <CaloDivider />
289
+ <TypographySpecimen roleName="title" role="title" sample="Tomorrow's lunch" usage="Major card titles" />
290
+ <CaloDivider />
291
+ <TypographySpecimen roleName="section" role="section" sample="Choose your meals" usage="Product modules" />
292
+ <CaloDivider />
293
+ <TypographySpecimen roleName="body" role="body" sample="Normal content copy" usage="Primary reading text" />
294
+ <CaloDivider />
295
+ <TypographySpecimen roleName="bodyMuted" role="bodyMuted" sample="Supporting copy" usage="Secondary explanations" />
296
+ <CaloDivider />
297
+ <TypographySpecimen roleName="caption" role="caption" sample="Compact label" usage="Component-owned labels" />
298
+ </CaloStack>
299
+ </VariantGroup>
300
+ <VariantGroup title="Desktop utility roles">
301
+ <CaloStack gap="size10">
302
+ <TypographySpecimen roleName="heading4" role="heading4" sample="Preview" usage="Pane headings and compact chrome" />
303
+ <CaloDivider />
304
+ <TypographySpecimen roleName="paragraphMedium" role="paragraphMedium" sample="Starter Prototype" usage="Sidebar rows and desktop copy" />
305
+ <CaloDivider />
306
+ <TypographySpecimen roleName="paragraphMediumStrong" role="paragraphMediumStrong" sample="Selected value" usage="Important compact values" />
307
+ <CaloDivider />
308
+ <TypographySpecimen roleName="captionSmall" role="captionSmall" sample="queued" usage="Metadata and quiet labels" />
309
+ </CaloStack>
310
+ </VariantGroup>
311
+ <VariantGroup title="Arabic roles">
312
+ {healthySansAvailable ? (
313
+ <CaloStack gap="size10">
314
+ <TypographySpecimen roleName="title · ar" role="title" language="ar" sample="وجبة الغداء" usage="Arabic major titles" />
315
+ <CaloDivider />
316
+ <TypographySpecimen roleName="body · ar" role="body" language="ar" sample="نص عربي لتفاصيل قصيرة." usage="Arabic content copy" />
317
+ <CaloDivider />
318
+ <TypographySpecimen roleName="captionSmall · ar" role="captionSmall" language="ar" sample="حالة مختصرة" usage="Arabic metadata" />
319
+ </CaloStack>
320
+ ) : (
321
+ <CaloText role="paragraphMedium" tone="danger">
322
+ Healthy Sans is not loaded in this runtime, so Arabic specimens are hidden until the font assets are available.
323
+ </CaloText>
324
+ )}
325
+ </VariantGroup>
326
+ </GallerySection>
327
+ ) : null}
328
+
329
+ {filterVisible("foundations") ? (
330
+ <GallerySection title="Carousel" label="Containers">
331
+ <VariantGroup title="Generic item container">
332
+ <CaloCarousel pageControl>
333
+ <CaloSurface padding="size20" elevation="raised" radius="radius6">
334
+ <CaloStack gap="size12">
335
+ <CaloInline align="center" justify="between">
336
+ <CaloBadge label="Plan" variant="brand" />
337
+ <CaloIcon common="calendar" size="md" tone="brand" />
338
+ </CaloInline>
339
+ <CaloStack gap="size8">
340
+ <CaloText role="section">Your next delivery</CaloText>
341
+ <CaloText role="bodyMuted">Breakfast and lunch arrive tomorrow between 8:00 - 9:00 AM.</CaloText>
342
+ </CaloStack>
343
+ </CaloStack>
344
+ </CaloSurface>
345
+ <CaloSurface padding="size20" elevation="raised" radius="radius6">
346
+ <CaloStack gap="size12">
347
+ <CaloInline align="center" justify="between">
348
+ <CaloBadge label="Cafe" variant="neutral" />
349
+ <CaloVisual name="smoothie-1" size="md" />
350
+ </CaloInline>
351
+ <CaloStack gap="size8">
352
+ <CaloText role="section">Cafe pickup</CaloText>
353
+ <CaloText role="bodyMuted">Keep campaign, product, or status cards in a stable horizontal rail.</CaloText>
354
+ </CaloStack>
355
+ </CaloStack>
356
+ </CaloSurface>
357
+ <CaloSurface padding="size20" elevation="raised" radius="radius6">
358
+ <CaloStack gap="size12">
359
+ <CaloInline align="center" justify="between">
360
+ <CaloBadge label="Rewards" variant="brand" />
361
+ <CaloIcon common="gift" size="md" tone="brand" />
362
+ </CaloInline>
363
+ <CaloStack gap="size8">
364
+ <CaloText role="section">Club balance</CaloText>
365
+ <CaloText role="bodyMuted">Use the carousel as the scroll owner; items keep their own content grammar.</CaloText>
366
+ </CaloStack>
367
+ </CaloStack>
368
+ </CaloSurface>
369
+ </CaloCarousel>
370
+ </VariantGroup>
371
+ <VariantGroup title="Promotion banner rail">
372
+ <CaloCarousel>
373
+ <CaloBanner
374
+ variant="promotion"
375
+ title="Summer smoothies are back"
376
+ description="Fresh blends with your next Cafe order."
377
+ image={smoothieImage}
378
+ tagLabel="Cafe offer"
379
+ onPress={previewOnly}
380
+ />
381
+ <CaloBanner
382
+ variant="promotion"
383
+ title="Fuel your training week"
384
+ description="High-protein meals matched to your goals."
385
+ image={mealImage}
386
+ tagLabel="Featured"
387
+ onPress={previewOnly}
388
+ />
389
+ </CaloCarousel>
390
+ </VariantGroup>
391
+ <VariantGroup title="Compact product rail">
392
+ <CaloCarousel itemSize="auto" pageControl={false}>
393
+ <CaloCafeProductCard
394
+ image={smoothieImage}
395
+ name="Iced Americano"
396
+ calories="0 cal"
397
+ price="2.8 BHD"
398
+ action="add"
399
+ scrolled
400
+ onPress={previewOnly}
401
+ onAddPress={previewOnly}
402
+ />
403
+ <CaloCafeProductCard
404
+ image={smoothieImage}
405
+ name="Iced Latte"
406
+ calories="125 cal"
407
+ price="2.8 BHD"
408
+ action="add"
409
+ scrolled
410
+ onPress={previewOnly}
411
+ onAddPress={previewOnly}
412
+ />
413
+ <CaloCafeProductCard
414
+ image={smoothieImage}
415
+ name="Iced Mocha Latte"
416
+ calories="210 cal"
417
+ price="2.2 BHD"
418
+ oldPrice="2.8 BHD"
419
+ action="add"
420
+ scrolled
421
+ onPress={previewOnly}
422
+ onAddPress={previewOnly}
423
+ />
424
+ <CaloCafeProductCard
425
+ image={smoothieImage}
426
+ name="Rose Latte"
427
+ calories="229 cal"
428
+ price="2.8 BHD"
429
+ discountLabel="Spring Menu"
430
+ action="add"
431
+ scrolled
432
+ onPress={previewOnly}
433
+ onAddPress={previewOnly}
434
+ />
435
+ </CaloCarousel>
436
+ </VariantGroup>
437
+ </GallerySection>
438
+ ) : null}
439
+
440
+ {filterVisible("navigation") ? (
441
+ <GallerySection title="Navigation" label="Bars">
442
+ <VariantGroup title="Top action bars">
443
+ <CaloStack gap="size12">
444
+ <CaloTopActionBar
445
+ title="Meal details"
446
+ leadingActions={[{ label: "Back", icon: <CaloIcon common="back" />, onPress: previewOnly }]}
447
+ trailingActions={[{ label: "Help", icon: <CaloIcon common="help" />, onPress: previewOnly }]}
448
+ divider
449
+ />
450
+ <CaloTopActionBar
451
+ title="Swap meal"
452
+ trailingActions={[{ label: "Done", presentation: "text", onPress: previewOnly }]}
453
+ divider
454
+ />
455
+ <CaloTopActionBar
456
+ progress={{ value: 2, max: 5, accessibilityLabel: "Step 2 of 5" }}
457
+ leadingActions={[{ label: "Back", icon: <CaloIcon common="back" />, onPress: previewOnly }]}
458
+ trailingActions={[{ label: "Help", icon: <CaloIcon common="help" />, onPress: previewOnly }]}
459
+ />
460
+ <CaloTopActionBar
461
+ leadingActions={[{ label: "Back", icon: <CaloIcon common="back" />, onPress: previewOnly }]}
462
+ trailingActions={[
463
+ { label: "Help", icon: <CaloIcon common="help" />, onPress: previewOnly },
464
+ { label: "Close", icon: <CaloIcon common="close" />, onPress: previewOnly }
465
+ ]}
466
+ />
467
+ <CaloTopActionBar
468
+ title="تفاصيل الوجبة"
469
+ language="ar"
470
+ leadingActions={[{ label: "Back", icon: <CaloIcon common="back" />, onPress: previewOnly }]}
471
+ trailingActions={[{ label: "Help", icon: <CaloIcon common="help" />, onPress: previewOnly }]}
472
+ divider
473
+ />
474
+ </CaloStack>
475
+ </VariantGroup>
476
+ <VariantGroup title="Top appbar recipes">
477
+ <CaloStack gap="size12">
478
+ <CaloContextTopBar
479
+ leadingAction={{ label: "Cancel", onPress: previewOnly }}
480
+ context={{ label: "Regular", value: "SAR 2.7" }}
481
+ trailingAction={{ label: "Done", onPress: previewOnly }}
482
+ />
483
+ <CaloCenteredTitleTopBar
484
+ title="Swap meal"
485
+ action={{ label: "Done", onPress: previewOnly }}
486
+ />
487
+ <CaloInlineHeader
488
+ eyebrow="Pickup from"
489
+ title="Seef Mall"
490
+ titleAction={{ label: "Change pickup", onPress: previewOnly }}
491
+ pillAction={{ label: "Rewards", icon: <CaloIcon common="gift" size="sm" tone="brand" weight="fill" />, onPress: previewOnly }}
492
+ iconAction={{ label: "Search", icon: <CaloIcon common="search" />, onPress: previewOnly }}
493
+ />
494
+ <CaloLargeTitleHeader
495
+ title="Market"
496
+ pillAction={{ label: "Rewards", icon: <CaloIcon common="gift" size="sm" tone="brand" weight="fill" />, onPress: previewOnly }}
497
+ iconAction={{ label: "AI search", icon: <CaloIcon common="sparkleSearch" />, onPress: previewOnly }}
498
+ />
499
+ <CaloSummaryHeader
500
+ eyebrow="Cafe rewards"
501
+ value="1234 points"
502
+ valueIcon={<CaloIcon common="gift" size="sm" tone="default" weight="fill" />}
503
+ leadingAction={{ label: "Back", icon: <CaloIcon common="back" />, onPress: previewOnly }}
504
+ />
505
+ <CaloLargeTitleHeader
506
+ title="Shopping bag"
507
+ iconAction={{ label: "Close", icon: <CaloIcon common="close" />, onPress: previewOnly }}
508
+ />
509
+ <CaloContextTopBar
510
+ language="ar"
511
+ leadingAction={{ label: "إلغاء", onPress: previewOnly }}
512
+ context={{ label: "عادي", value: "SAR 2.7" }}
513
+ trailingAction={{ label: "تم", onPress: previewOnly }}
514
+ />
515
+ <CaloLargeTitleHeader
516
+ language="ar"
517
+ title="الماركت"
518
+ pillAction={{ label: "مكآفات", icon: <CaloIcon common="gift" size="sm" tone="brand" weight="fill" />, iconPosition: "trailing", onPress: previewOnly }}
519
+ iconAction={{ label: "بحث ذكي", icon: <CaloIcon common="sparkleSearch" />, onPress: previewOnly }}
520
+ />
521
+ </CaloStack>
522
+ </VariantGroup>
523
+ <VariantGroup title="Main hub bottom navigation">
524
+ <CaloBottomNavigation selectedId={bottomNavigationTab} onSelect={setBottomNavigationTab} />
525
+ </VariantGroup>
526
+ <VariantGroup title="Context screen">
527
+ <CaloContextScreen
528
+ title="Meal plan paused"
529
+ description="Your meal deliveries are paused from Tuesday, 29 June. Resume now to continue receiving your favorite meals!"
530
+ visual={<CaloVisual name="pause" size="lg" />}
531
+ footer={<CaloButton label="Resume deliveries" size="large" fullWidth onPress={previewOnly} />}
532
+ topBar={
533
+ <CaloLargeTitleHeader
534
+ title="Paused"
535
+ pillAction={{ label: "Refer & earn", icon: <CaloIcon common="gift" size="sm" tone="brand" weight="fill" />, iconPosition: "leading", onPress: previewOnly }}
536
+ />
537
+ }
538
+ bottomBar={<CaloBottomNavigation selectedId="meals" onSelect={() => previewOnly()} />}
539
+ />
540
+ </VariantGroup>
541
+ <VariantGroup title="Date bars">
542
+ <CaloStack gap="size12">
543
+ <CaloDateBar
544
+ selectedId={selectedDate}
545
+ onSelect={setSelectedDate}
546
+ items={[
547
+ { id: "may-25", label: "25", dayLabel: "F" },
548
+ { id: "may-26", label: "26", dayLabel: "S" },
549
+ { id: "may-27", label: "27", dayLabel: "S" },
550
+ { id: "may-28", label: "28", dayLabel: "M", state: "today" },
551
+ { id: "may-29", label: "29", dayLabel: "T" },
552
+ { id: "may-30", label: "30", dayLabel: "W" },
553
+ { id: "may-31", label: "31", dayLabel: "T" }
554
+ ]}
555
+ />
556
+ <CaloDateBar
557
+ sticky
558
+ selectedId="fri"
559
+ items={[
560
+ { id: "fri", label: "25", dayLabel: "F" },
561
+ { id: "sat", label: "26", dayLabel: "S" },
562
+ { id: "sun", label: "27", dayLabel: "S" },
563
+ { id: "mon", label: "28", dayLabel: "M", state: "today" },
564
+ { id: "tue", label: "29", dayLabel: "T" },
565
+ { id: "wed", label: "30", dayLabel: "W" },
566
+ { id: "thu", label: "31", dayLabel: "T" }
567
+ ]}
568
+ />
569
+ <CaloInline align="center" gap="size12" wrap>
570
+ <CaloDateBarItem id="skip" label="24" dayLabel="S" status="skip" />
571
+ <CaloDateBarItem id="pause-one" label="24" dayLabel="S" status="pause" />
572
+ <CaloDateBarItem id="pause-range" label="24" endLabel="29" status="pause" state="selected" />
573
+ <CaloDateBarItem id="expired" label="24" dayLabel="S" status="expired" />
574
+ <CaloDateBarItem id="exclude" label="20" endLabel="24" status="exclude" state="selected" />
575
+ <CaloDateBarItem id="arabic" label="24" dayLabel="س" status="skip" language="ar" state="selected" />
576
+ </CaloInline>
577
+ </CaloStack>
578
+ </VariantGroup>
579
+ </GallerySection>
580
+ ) : null}
581
+
582
+ {filterVisible("actions") ? (
583
+ <GallerySection title="Actions" label="Buttons">
584
+ <VariantGroup title="Button variants">
585
+ <CaloInline align="center" gap="size10" wrap>
586
+ <CaloButton label="Continue" onPress={previewOnly} />
587
+ <CaloButton label="Preview" variant="secondary" onPress={previewOnly} />
588
+ <CaloButton label="Tools" variant="utility" onPress={previewOnly} />
589
+ <CaloButton label="Remove" variant="danger" onPress={previewOnly} />
590
+ <CaloButton label="Learn more" variant="link" onPress={previewOnly} />
591
+ </CaloInline>
592
+ </VariantGroup>
593
+ <VariantGroup title="Button sizes">
594
+ <CaloStack gap="size10">
595
+ <ButtonSizeRow label="Large">
596
+ <CaloButton label="Continue" size="large" onPress={previewOnly} />
597
+ <CaloButton label="Preview" variant="secondary" size="large" onPress={previewOnly} />
598
+ </ButtonSizeRow>
599
+ <CaloDivider />
600
+ <ButtonSizeRow label="Medium">
601
+ <CaloButton label="Continue" size="medium" onPress={previewOnly} />
602
+ <CaloButton label="Preview" variant="secondary" size="medium" onPress={previewOnly} />
603
+ </ButtonSizeRow>
604
+ <CaloDivider />
605
+ <ButtonSizeRow label="Small">
606
+ <CaloButton label="Continue" size="small" onPress={previewOnly} />
607
+ <CaloButton label="Preview" variant="secondary" size="small" onPress={previewOnly} />
608
+ </ButtonSizeRow>
609
+ </CaloStack>
610
+ </VariantGroup>
611
+ <VariantGroup title="Icon button variants">
612
+ <CaloInline align="center" gap="size10" wrap>
613
+ <CaloIconButton label="Confirm" icon={<CaloIcon common="check" />} variant="primary" onPress={previewOnly} />
614
+ <CaloIconButton label="Search" icon={<CaloIcon common="search" />} variant="secondary" onPress={previewOnly} />
615
+ <CaloIconButton label="Settings" icon={<CaloIcon common="settings" />} variant="neutral" onPress={previewOnly} />
616
+ <CaloIconButton label="Filter" icon={<CaloIcon common="filter" />} variant="outline" onPress={previewOnly} />
617
+ <CaloIconButton label="Delete" icon={<CaloIcon common="remove" />} variant="danger" onPress={previewOnly} />
618
+ </CaloInline>
619
+ </VariantGroup>
620
+ <VariantGroup title="Icon button sizes">
621
+ <CaloStack gap="size10">
622
+ <ButtonSizeRow label="Large">
623
+ <CaloIconButton label="Large confirm" icon={<CaloIcon common="check" />} variant="primary" size="large" onPress={previewOnly} />
624
+ <CaloIconButton label="Large filter" icon={<CaloIcon common="filter" />} variant="outline" size="large" onPress={previewOnly} />
625
+ </ButtonSizeRow>
626
+ <CaloDivider />
627
+ <ButtonSizeRow label="Medium">
628
+ <CaloIconButton label="Medium confirm" icon={<CaloIcon common="check" />} variant="primary" size="medium" onPress={previewOnly} />
629
+ <CaloIconButton label="Medium filter" icon={<CaloIcon common="filter" />} variant="outline" size="medium" onPress={previewOnly} />
630
+ </ButtonSizeRow>
631
+ <CaloDivider />
632
+ <ButtonSizeRow label="Small">
633
+ <CaloIconButton label="Small confirm" icon={<CaloIcon common="check" />} variant="primary" size="small" onPress={previewOnly} />
634
+ <CaloIconButton label="Small filter" icon={<CaloIcon common="filter" />} variant="outline" size="small" onPress={previewOnly} />
635
+ </ButtonSizeRow>
636
+ </CaloStack>
637
+ </VariantGroup>
638
+ <VariantGroup title="Bottom action bars">
639
+ <CaloStack gap="size12">
640
+ <CaloBottomActionBar primaryAction={{ label: "Continue", onPress: previewOnly }} divider />
641
+ <CaloBottomActionBar
642
+ primaryAction={{ label: "Apply", onPress: previewOnly }}
643
+ secondaryAction={{ label: "Clear", onPress: previewOnly }}
644
+ layout="horizontal"
645
+ divider
646
+ />
647
+ <CaloBottomActionBar
648
+ primaryAction={{ label: "Continue", disabled: true, onPress: previewOnly }}
649
+ secondaryAction={{ label: "I'm okay with everything", onPress: previewOnly }}
650
+ actionOrder="secondaryFirst"
651
+ layout="verticalTertiary"
652
+ divider
653
+ />
654
+ <CaloBottomActionBar
655
+ primaryAction={{ label: "Add", priceLabel: "2.8 BHD", onPress: previewOnly }}
656
+ summary={{ amount: { value: "2,200", unit: "AED", suffix: "per month" }, description: "VAT inclusive" }}
657
+ divider
658
+ />
659
+ <CaloBottomActionBar
660
+ primaryAction={{ label: "Pay with", paymentMark: "applePay", onPress: previewOnly }}
661
+ summary={{ amount: { value: "2,420", currency: "SAR" }, description: "VAT inclusive" }}
662
+ divider
663
+ />
664
+ <CaloBottomActionBar
665
+ primaryAction={{ label: "Add", priceLabel: "2.8 BHD", onPress: previewOnly }}
666
+ stepper={{
667
+ value: actionBarQuantity,
668
+ onIncrement: () => setActionBarQuantity((value) => value + 1),
669
+ onDecrement: () => setActionBarQuantity((value) => Math.max(0, value - 1))
670
+ }}
671
+ divider
672
+ />
673
+ </CaloStack>
674
+ </VariantGroup>
675
+ <VariantGroup title="Bottom sheets">
676
+ <CaloStack gap="size20">
677
+ <CaloButton label="Open real draggable sheet" variant="secondary" onPress={() => setModalSheetOpen(true)} />
678
+ <CaloBottomSheet
679
+ open={modalSheetOpen}
680
+ onOpenChange={setModalSheetOpen}
681
+ presentation="modal"
682
+ type="information"
683
+ title="Real bottom sheet"
684
+ description="This sheet slides in, drags down, and dismisses through Gorhom."
685
+ visual={<CaloVisual name="green-gift" size="xl" />}
686
+ primaryAction={{ label: "Close", onPress: () => setModalSheetOpen(false) }}
687
+ />
688
+ <CaloBottomSheet
689
+ width="figma"
690
+ title="Maximum 1 line title"
691
+ description="Use this space to put a maximum of two lines of your description text."
692
+ primaryAction={{ label: "Label", onPress: previewOnly }}
693
+ >
694
+ <CaloSurface padding="size20" radius="radius3">
695
+ <CaloText role="paragraphMedium" tone="muted">Replace this content</CaloText>
696
+ </CaloSurface>
697
+ </CaloBottomSheet>
698
+ <CaloBottomSheet
699
+ width="figma"
700
+ type="information"
701
+ title="Title goes here"
702
+ description="Use this line for the description text."
703
+ visual={<CaloVisual name="green-gift" size="xl" />}
704
+ primaryAction={{ label: "Label", onPress: previewOnly }}
705
+ />
706
+ <CaloBottomSheet
707
+ width="figma"
708
+ title="العنوان يذهب هنا"
709
+ description="استخدم هذه المساحة لوصف مختصر."
710
+ language="ar"
711
+ primaryAction={{ label: "استمرار", onPress: previewOnly }}
712
+ >
713
+ <CaloStack gap="size12">
714
+ <CaloText role="paragraphMedium" language="ar">استبدل هذا المحتوى.</CaloText>
715
+ <CaloText role="paragraphMedium" language="ar" tone="muted">يحافظ المكون على التمرير والكروم القياسي دون حالة محلية.</CaloText>
716
+ </CaloStack>
717
+ </CaloBottomSheet>
718
+ </CaloStack>
719
+ </VariantGroup>
720
+ </GallerySection>
721
+ ) : null}
722
+
723
+ {filterVisible("actions") ? (
724
+ <GallerySection title="Compact States" label="Badges">
725
+ <VariantGroup title="Large badges">
726
+ <CaloInline align="center" gap="size8" wrap>
727
+ <CaloBadge label="Applied" variant="brand" size="big" leadingIcon trailingIcon />
728
+ <CaloBadge label="Draft" variant="neutral" size="big" leadingIcon />
729
+ <CaloBadge label="Remove" variant="danger" size="big" trailingIcon />
730
+ <CaloBadge label="Meal plans" variant="outline" size="big" />
731
+ <CaloBadge label="VIP" variant="custom" size="big" />
732
+ </CaloInline>
733
+ </VariantGroup>
734
+ <VariantGroup title="Small badges">
735
+ <CaloInline align="center" gap="size8" wrap>
736
+ <CaloBadge label="Fresh" variant="brand" />
737
+ <CaloBadge label="Info" variant="neutral" />
738
+ <CaloBadge label="Risk" variant="danger" />
739
+ <CaloBadge label="Filter" variant="outline" />
740
+ <CaloBadge label="Special" variant="custom" />
741
+ </CaloInline>
742
+ </VariantGroup>
743
+ </GallerySection>
744
+ ) : null}
745
+
746
+ {filterVisible("selection") ? (
747
+ <GallerySection title="Selection" label="Controls">
748
+ <VariantGroup title="Tab menu groups">
749
+ <CaloStack gap="size12">
750
+ <CaloTabMenuGroup items={tabMenuOptions.slice(0, 2)} selectedId={outlineTab} onSelect={setOutlineTab} />
751
+ <CaloTabMenuGroup items={tabMenuOptions} selectedId={outlineTab} onSelect={setOutlineTab} width="hug" />
752
+ <CaloTabMenuGroup items={chipMenuOptions.slice(0, 2)} selectedId={chipTab} onSelect={setChipTab} variant="chips" />
753
+ <CaloTabMenuGroup items={chipMenuOptions} selectedId={chipTab} onSelect={setChipTab} variant="chips" width="hug" />
754
+ </CaloStack>
755
+ </VariantGroup>
756
+ <CaloInline align="center" gap="size16" wrap>
757
+ <SelectionPreview label="Regular meal">
758
+ <CaloRadio selected={radioSelected === "regular"} label="Regular meal" onSelectedChange={() => setRadioSelected("regular")} />
759
+ </SelectionPreview>
760
+ <SelectionPreview label="Low carb">
761
+ <CaloRadio selected={radioSelected === "low-carb"} label="Low carb" onSelectedChange={() => setRadioSelected("low-carb")} />
762
+ </SelectionPreview>
763
+ <SelectionPreview label="Add cutlery">
764
+ <CaloCheckbox selected={checkboxSelected} label="Add cutlery" onSelectedChange={setCheckboxSelected} />
765
+ </SelectionPreview>
766
+ <SelectionPreview label="Disabled">
767
+ <CaloCheckbox selected disabled />
768
+ </SelectionPreview>
769
+ </CaloInline>
770
+ <CaloInline align="center" gap="size16" wrap>
771
+ <CaloToggle label="Enable meals" selected={toggleSelected} onSelectedChange={setToggleSelected} />
772
+ <CaloToggle label="Loading selected" selected loading onSelectedChange={previewOnly} />
773
+ </CaloInline>
774
+ <VariantGroup title="Selection card radio group">
775
+ <CaloInline align="start" gap="size16" wrap>
776
+ <CaloSelectionCardRadioGroup
777
+ selectedId={selectionCardOption}
778
+ onSelect={setSelectionCardOption}
779
+ options={[
780
+ { id: "daily", title: "Daily delivery", description: "Meals arrive every day at your chosen time." },
781
+ { id: "weekly", title: "Weekly delivery", description: "Get a weekly box with meals ready to heat." },
782
+ { id: "paused", title: "Pause deliveries", description: "Temporarily stop meals while you travel.", disabled: true }
783
+ ]}
784
+ />
785
+ <CaloSelectionCardRadioGroup
786
+ language="ar"
787
+ selectedId={selectionCardOption}
788
+ onSelect={setSelectionCardOption}
789
+ options={[
790
+ { id: "daily", title: "توصيل يومي", description: "تصل وجباتك كل يوم في الوقت الذي تختاره." },
791
+ { id: "weekly", title: "توصيل أسبوعي", description: "استلم صندوقا أسبوعيا بوجبات جاهزة للتسخين." },
792
+ { id: "paused", title: "إيقاف التوصيل مؤقتا", description: "أوقف الوجبات مؤقتا أثناء السفر.", disabled: true }
793
+ ]}
794
+ />
795
+ <CaloSelectionCardRadioItem title="Standalone row" description="Use only when another component owns the group shell." selected onPress={previewOnly} />
796
+ </CaloInline>
797
+ </VariantGroup>
798
+ <VariantGroup title="Selection card recipes">
799
+ <CaloInline align="start" gap="size16" wrap>
800
+ <CaloSelectionCard
801
+ title="Option title"
802
+ description="Descriptive text goes here"
803
+ selected={selectionCardChoice === "visual"}
804
+ visual="face-savoring-food"
805
+ visualSize="small"
806
+ onPress={() => setSelectionCardChoice("visual")}
807
+ />
808
+ <CaloSelectionCard
809
+ title="Option title"
810
+ selected={selectionCardChoice === "control"}
811
+ control="checkbox"
812
+ density="compact"
813
+ onPress={() => setSelectionCardChoice("control")}
814
+ />
815
+ <CaloSelectionCard
816
+ title="Smoothie"
817
+ description="Description goes here"
818
+ selected={selectionCardChoice === "value"}
819
+ visual="smoothie-l"
820
+ visualPosition="leading"
821
+ visualSize="medium"
822
+ trailingMeta={<CaloSelectionCardPrice value="2.7" currency="BHD" oldValue="3.1 BHD" />}
823
+ onPress={() => setSelectionCardChoice("value")}
824
+ />
825
+ <CaloSelectionCard
826
+ title="club"
827
+ titleTone="brand"
828
+ titleVariant="program"
829
+ selected={selectionCardChoice === "program"}
830
+ width="full"
831
+ titleTrailingMeta={
832
+ <CaloToggle
833
+ label="Join program"
834
+ selected={selectionCardChoice === "program"}
835
+ onSelectedChange={(selected) => setSelectionCardChoice(selected ? "program" : "value")}
836
+ />
837
+ }
838
+ bodyMeta={
839
+ <CaloInline align="center" justify="between" gap="size16">
840
+ <CaloInline align="center" gap="size4" wrap>
841
+ <CaloText role="paragraphLargeStrong">Save</CaloText>
842
+ <CaloPriceText value="440" currency="SAR" tone="brand" size="medium" />
843
+ <CaloText role="paragraphLargeStrong">by joining Club</CaloText>
844
+ </CaloInline>
845
+ <CaloPriceText value="699" currency="SAR" size="medium" />
846
+ </CaloInline>
847
+ }
848
+ onPress={() => setSelectionCardChoice("program")}
849
+ />
850
+ <CaloSelectionCard
851
+ title="For myself"
852
+ description="Descriptive explanatory text lines go here"
853
+ selected={selectionCardChoice === "media-control"}
854
+ control="checkbox"
855
+ density="media"
856
+ visual="meal-single-1"
857
+ visualSize="large"
858
+ onPress={() => setSelectionCardChoice("media-control")}
859
+ />
860
+ <CaloSelectionCard
861
+ title="Bag 2"
862
+ selected={selectionCardChoice === "metric-summary"}
863
+ control="checkbox"
864
+ density="media"
865
+ bodyMeta={<CaloSelectionCardMacros calories="1234 kcal" macros={[{ label: "Protein", value: "120g", tone: "protein" }, { label: "Carbs", value: "123g", tone: "carbs" }, { label: "Fat", value: "123g", tone: "fat" }]} />}
866
+ onPress={() => setSelectionCardChoice("metric-summary")}
867
+ />
868
+ <CaloSelectionCard
869
+ language="ar"
870
+ title="عنوان الخيار"
871
+ description="النص الوصفي يظهر هنا"
872
+ selected={selectionCardChoice === "arabic"}
873
+ control="checkbox"
874
+ visual="meal-single-1"
875
+ visualSize="large"
876
+ density="media"
877
+ onPress={() => setSelectionCardChoice("arabic")}
878
+ />
879
+ </CaloInline>
880
+ </VariantGroup>
881
+ </GallerySection>
882
+ ) : null}
883
+
884
+ {filterVisible("selection") ? (
885
+ <GallerySection title="Quantity" label="Steppers">
886
+ <VariantGroup title="Stepper variants">
887
+ <CaloInline align="center" gap="size12" wrap>
888
+ <CaloStepper value={stepperValue} size="large" onIncrement={() => setStepperValue((value) => value + 1)} onDecrement={() => setStepperValue((value) => Math.max(0, value - 1))} />
889
+ <CaloStepper value={compactValue} variant="compact" compactMode="auto" onIncrement={() => setCompactValue((value) => value + 1)} onDecrement={() => setCompactValue((value) => Math.max(0, value - 1))} />
890
+ <CaloStepper value={1} variant="compact" compactMode="delete" onIncrement={previewOnly} onDecrement={previewOnly} />
891
+ </CaloInline>
892
+ </VariantGroup>
893
+ </GallerySection>
894
+ ) : null}
895
+
896
+ {filterVisible("forms") ? (
897
+ <GallerySection title="Inputs" label="Forms">
898
+ <CaloInput label="Full name" required helperText="Use the name shown on the account." value={fieldValue} onChangeText={setFieldValue} />
899
+ <CaloTextArea label="Delivery note" optional maxLength={120} value={noteValue} onChangeText={setNoteValue} />
900
+ <CaloVerificationCodeInput value={codeValue} helperText="Enter the 6 digit code sent to your phone." onChangeText={setCodeValue} />
901
+ <CaloPhoneInput value={phoneValue} helperText="We only use this for delivery updates." onChangeText={setPhoneValue} />
902
+ <CaloCardDetailsInput
903
+ cardNumber={cardNumber}
904
+ expiry={expiry}
905
+ cvv={cvv}
906
+ helperText="Card details stay grouped as one payment object."
907
+ onCardNumberChange={setCardNumber}
908
+ onExpiryChange={setExpiry}
909
+ onCvvChange={setCvv}
910
+ />
911
+ <CaloPromoCodeInput value={promoValue} state="applied" helperText="Promo codes use the ticket-leading field with a right action." onChangeText={setPromoValue} onApply={previewOnly} />
912
+ </GallerySection>
913
+ ) : null}
914
+
915
+ {filterVisible("feedback") ? (
916
+ <GallerySection title="Feedback" label="Toast">
917
+ <CaloToast message="Your plan was updated successfully." state="success" onDismiss={previewOnly} />
918
+ <CaloToast message="Some meals are unavailable for this branch." state="warning" onDismiss={previewOnly} />
919
+ <CaloToast message="Payment failed. Try another method." state="error" onDismiss={previewOnly} />
920
+ </GallerySection>
921
+ ) : null}
922
+
923
+ {filterVisible("commerce") ? (
924
+ <GallerySection title="Offers" label="Voucher">
925
+ <VariantGroup title="Banners">
926
+ <CaloStack gap="size12">
927
+ <CaloBanner
928
+ variant="optIn"
929
+ title="Unlock 7 days offer"
930
+ description={[
931
+ "Get ",
932
+ { text: "10% off +", strong: true },
933
+ " ",
934
+ { text: "free daily juice", strong: true },
935
+ " worth 366 SAR with monthly & weekly plan."
936
+ ]}
937
+ visualName="smoothie-1"
938
+ control={{ type: "toggle", label: "Unlock 7 days offer", selected: bannerSelected, onSelectedChange: setBannerSelected }}
939
+ />
940
+ <CaloBanner
941
+ variant="optIn"
942
+ title="عرض فتح لمدة 7 أيام"
943
+ description="احصل على خصم 10% + عصير مجاني يومياً بقيمة 366 ريال سعودي مع الخطة الشهرية والأسبوعية."
944
+ language="ar"
945
+ selected
946
+ visualName="smoothie-1"
947
+ control={{ type: "toggle", label: "عرض فتح لمدة 7 أيام", selected: true, onSelectedChange: previewOnly }}
948
+ />
949
+ <CaloBanner
950
+ variant="action"
951
+ state="warning"
952
+ title={["Your plan is ending in ", { text: "1 day", tone: "warning" }, "!"]}
953
+ description="Today is your last delivery. Renew now to continue your meal plan."
954
+ action={{ label: "Renew my plan", onPress: previewOnly }}
955
+ onDismiss={previewOnly}
956
+ visualName="meal-tray-fork"
957
+ />
958
+ <CaloBanner
959
+ variant="action"
960
+ title="You skipped this bag"
961
+ description="You skipped this bag's delivery so there are no meals for us to show you."
962
+ action={{ label: "Unskip bag", onPress: previewOnly }}
963
+ onDismiss={previewOnly}
964
+ visualName="calo-bag-nice-today"
965
+ />
966
+ </CaloStack>
967
+ </VariantGroup>
968
+ <VariantGroup title="Promotion banners">
969
+ <CaloStack gap="size16">
970
+ <CaloCarousel>
971
+ <CaloBanner
972
+ variant="promotion"
973
+ title="Summer smoothies are back"
974
+ description="Fresh blends with your next Cafe order."
975
+ image={smoothieImage}
976
+ tagLabel="Cafe offer"
977
+ onPress={previewOnly}
978
+ />
979
+ <CaloBanner
980
+ variant="promotion"
981
+ title="Fuel your training week"
982
+ description="High-protein meals matched to your goals."
983
+ image={mealImage}
984
+ tagLabel="Featured"
985
+ onPress={previewOnly}
986
+ />
987
+ </CaloCarousel>
988
+ <CaloBanner
989
+ variant="promotion"
990
+ title="Summer smoothies are back"
991
+ description="Fresh blends with your next Cafe order."
992
+ image={smoothieImage}
993
+ tagLabel="Cafe offer"
994
+ onPress={previewOnly}
995
+ onDismiss={previewOnly}
996
+ />
997
+ <CaloInline gap="size16" wrap>
998
+ <CaloBanner
999
+ variant="promotion"
1000
+ title="Fuel your training week"
1001
+ description="High-protein meals matched to your goals."
1002
+ image={mealImage}
1003
+ tagLabel="Featured"
1004
+ onPress={previewOnly}
1005
+ />
1006
+ <CaloBanner
1007
+ variant="promotion"
1008
+ title="عودة عروض العصائر"
1009
+ description="خلطات منعشة مع طلبك القادم من كافيه."
1010
+ image={smoothieImage}
1011
+ language="ar"
1012
+ tagLabel="عرض كافيه"
1013
+ onPress={previewOnly}
1014
+ onDismiss={previewOnly}
1015
+ />
1016
+ </CaloInline>
1017
+ </CaloStack>
1018
+ </VariantGroup>
1019
+ <VariantGroup title="Voucher states">
1020
+ <CaloVoucherCard title="Get 20% off" condition="Applies to orders above 12 BHD" state="available" onPress={previewOnly} onInfoPress={previewOnly} />
1021
+ <CaloVoucherCard title="Free smoothie" condition="Applied to your next delivery" state="applied" visualName="smoothie-1" onPress={previewOnly} />
1022
+ <CaloVoucherCard title="Club reward" condition="This voucher is no longer valid" state="disabled" visualName="green-gift" />
1023
+ </VariantGroup>
1024
+ </GallerySection>
1025
+ ) : null}
1026
+
1027
+ {filterVisible("commerce") ? (
1028
+ <GallerySection title="Delivery Status" label="Home">
1029
+ <VariantGroup title="Expanded active states">
1030
+ <CaloDeliveryStatusCard status="active" onSkipPress={previewOnly} onToggleExpanded={previewOnly} />
1031
+ <CaloDeliveryStatusCard status="active-address-time" onSkipPress={previewOnly} onToggleExpanded={previewOnly} />
1032
+ <CaloDeliveryStatusCard status="active-address" onSkipPress={previewOnly} onToggleExpanded={previewOnly} />
1033
+ </VariantGroup>
1034
+ <VariantGroup title="Expanded progress states">
1035
+ <CaloDeliveryStatusCard status="ready" onToggleExpanded={previewOnly} />
1036
+ <CaloDeliveryStatusCard status="on-the-way" statusText="Your driver is on the way" onToggleExpanded={previewOnly} />
1037
+ <CaloDeliveryStatusCard status="nearby" statusText="Nearby, delivering to Home" etaText="Arriving by 8:45 - 9:00 AM" onToggleExpanded={previewOnly} />
1038
+ <CaloDeliveryStatusCard status="delay" statusText="Running a bit late, but it's coming!" onToggleExpanded={previewOnly} />
1039
+ </VariantGroup>
1040
+ <VariantGroup title="Collapsed states">
1041
+ <CaloDeliveryStatusCard status="active" defaultExpanded={false} onToggleExpanded={previewOnly} />
1042
+ <CaloDeliveryStatusCard status="active-address-time" defaultExpanded={false} onToggleExpanded={previewOnly} />
1043
+ <CaloDeliveryStatusCard status="active-address" defaultExpanded={false} onToggleExpanded={previewOnly} />
1044
+ <CaloDeliveryStatusCard status="ready" defaultExpanded={false} onToggleExpanded={previewOnly} />
1045
+ <CaloDeliveryStatusCard status="on-the-way" defaultExpanded={false} onToggleExpanded={previewOnly} />
1046
+ <CaloDeliveryStatusCard status="nearby" defaultExpanded={false} onToggleExpanded={previewOnly} />
1047
+ <CaloDeliveryStatusCard status="delay" defaultExpanded={false} onToggleExpanded={previewOnly} />
1048
+ </VariantGroup>
1049
+ <VariantGroup title="Terminal states">
1050
+ <CaloDeliveryStatusCard status="delivered-rating" onNegativeRatingPress={previewOnly} onPositiveRatingPress={previewOnly} />
1051
+ <CaloDeliveryStatusCard status="delivered-done" deliveredAtText="Delivered at 9:00 PM to Home" />
1052
+ <CaloDeliveryStatusCard status="skipped" statusText="You skipped this delivery" deliveredAtText="No meals scheduled for this bag" />
1053
+ <CaloDeliveryStatusCard status="cancelled" statusText="This delivery was cancelled" deliveredAtText="Contact support if this looks wrong" />
1054
+ <CaloDeliveryStatusCard status="failed" statusText="We couldn't deliver this bag" deliveredAtText="Contact support to reschedule" />
1055
+ </VariantGroup>
1056
+ <VariantGroup title="Small screen">
1057
+ <CaloDeliveryStatusCard status="active" size="compact" onSkipPress={previewOnly} onToggleExpanded={previewOnly} />
1058
+ <CaloDeliveryStatusCard status="active" defaultExpanded={false} size="compact" onToggleExpanded={previewOnly} />
1059
+ <CaloDeliveryStatusCard status="nearby" size="compact" statusText="Nearby, delivering to Home" etaText="Arriving by 8:45 - 9:00 AM" onToggleExpanded={previewOnly} />
1060
+ <CaloDeliveryStatusCard status="delivered-done" size="compact" deliveredAtText="Delivered at 9:00 PM to Home" />
1061
+ </VariantGroup>
1062
+ <VariantGroup title="Arabic states">
1063
+ <CaloDeliveryStatusCard language="ar" status="active-address-time" onSkipPress={previewOnly} onToggleExpanded={previewOnly} />
1064
+ <CaloDeliveryStatusCard language="ar" status="active-address-time" defaultExpanded={false} onToggleExpanded={previewOnly} />
1065
+ <CaloDeliveryStatusCard language="ar" status="nearby" statusText="قريب منك، يتم التوصيل إلى المنزل" etaText="يصل بين 8:45 - 9:00 ص" onToggleExpanded={previewOnly} />
1066
+ <CaloDeliveryStatusCard language="ar" status="delivered-rating" onNegativeRatingPress={previewOnly} onPositiveRatingPress={previewOnly} />
1067
+ <CaloDeliveryStatusCard language="ar" status="delivered-done" deliveredAtText="تم التوصيل الساعة 9:00 م إلى المنزل" />
1068
+ <CaloDeliveryStatusCard language="ar" status="failed" statusText="تعذر توصيل هذه الحقيبة" deliveredAtText="تواصل مع الدعم لإعادة الجدولة" />
1069
+ </VariantGroup>
1070
+ </GallerySection>
1071
+ ) : null}
1072
+
1073
+ {filterVisible("commerce") ? (
1074
+ <GallerySection title="Payment" label="Methods">
1075
+ <CaloPaymentMethodList
1076
+ selectedId={paymentMethodId}
1077
+ onSelect={setPaymentMethodId}
1078
+ footerAction={{
1079
+ label: "See all payment methods",
1080
+ onPress: previewOnly,
1081
+ trailingIcon: <CaloIcon name="CaretDown" tone="brand" size="sm" />
1082
+ }}
1083
+ options={[
1084
+ { id: "wallet", type: "wallet", balance: "10 BHD" },
1085
+ { id: "apple-pay", type: "applePay" },
1086
+ { id: "saved-card", type: "savedCard", brand: "visa", last4: "4242" },
1087
+ { id: "new-card", type: "newCard" },
1088
+ { id: "tabby", type: "tabby", descriptionEmphasis: "Learn more", descriptionEmphasisTone: "brand" },
1089
+ { id: "tamara", type: "tamara" }
1090
+ ]}
1091
+ newCardDetails={{
1092
+ cardNumber,
1093
+ expiry,
1094
+ cvv,
1095
+ onCardNumberChange: setCardNumber,
1096
+ onExpiryChange: setExpiry,
1097
+ onCvvChange: setCvv,
1098
+ helperText: "We will verify your card with a small temporary charge."
1099
+ }}
1100
+ />
1101
+ </GallerySection>
1102
+ ) : null}
1103
+
1104
+ {filterVisible("food") ? (
1105
+ <GallerySection title="Dietary Cards" label="Food">
1106
+ <CaloInline gap="size16" wrap>
1107
+ <CaloDietaryCard type="balanced" selected={dietaryPlan === "balanced"} onPress={() => setDietaryPlan("balanced")} />
1108
+ <CaloDietaryCard type="highProtein" selected={dietaryPlan === "highProtein"} onPress={() => setDietaryPlan("highProtein")} />
1109
+ <CaloDietaryCard type="personalized" selected={dietaryPlan === "personalized"} onPress={() => setDietaryPlan("personalized")} onEditMacrosPress={previewOnly} />
1110
+ <CaloDietaryCard type="lowCarb" language="ar" selected={dietaryPlan === "lowCarb"} onPress={() => setDietaryPlan("lowCarb")} />
1111
+ </CaloInline>
1112
+ </GallerySection>
1113
+ ) : null}
1114
+
1115
+ {filterVisible("food") ? (
1116
+ <GallerySection title="Category Tile Grid" label="Food">
1117
+ <CaloCategoryTileGrid items={cafeCategoryItems} onSelect={previewOnly} />
1118
+ </GallerySection>
1119
+ ) : null}
1120
+
1121
+ {filterVisible("food") ? (
1122
+ <GallerySection title="Meal Cards" label="Food">
1123
+ <CaloMacroSummary macros={macros} />
1124
+ <CaloFoodPreferenceWarning label="Contains dairy" tone="warning" />
1125
+ <CaloInline align="center" gap="size8" wrap>
1126
+ <CaloRatingChip label="Loved it" selected={rating === "Loved it"} onPress={() => setRating("Loved it")} />
1127
+ <CaloRatingChip label="Okay" selected={rating === "Okay"} onPress={() => setRating("Okay")} />
1128
+ </CaloInline>
1129
+ <CaloInline align="start" gap="size12" wrap>
1130
+ <CaloCompactVariantTile
1131
+ image={sauceImage}
1132
+ title="Rose Sauce"
1133
+ meta="120 kcal"
1134
+ selected={selectedVariant === "rose"}
1135
+ onPress={() => setSelectedVariant("rose")}
1136
+ />
1137
+ <CaloCompactVariantTile
1138
+ image={sauceImage}
1139
+ title="Spicy Sauce"
1140
+ meta="80 kcal"
1141
+ selected={selectedVariant === "spicy"}
1142
+ onPress={() => setSelectedVariant("spicy")}
1143
+ />
1144
+ <CaloCompactVariantTile
1145
+ image={sauceImage}
1146
+ title="صلصة الورد"
1147
+ meta="120 ك.كال"
1148
+ language="ar"
1149
+ selected={selectedVariant === "arabic"}
1150
+ onPress={() => setSelectedVariant("arabic")}
1151
+ />
1152
+ </CaloInline>
1153
+ <CaloProductTile
1154
+ image={smoothieImage}
1155
+ name="Mango protein smoothie"
1156
+ calories="210 Cal"
1157
+ price="2.4 BHD"
1158
+ badges={[{ label: "New", variant: "brand" }]}
1159
+ favorite
1160
+ quantity={compactValue}
1161
+ onFavoritePress={previewOnly}
1162
+ onIncrement={() => setCompactValue((value) => value + 1)}
1163
+ onDecrement={() => setCompactValue((value) => Math.max(0, value - 1))}
1164
+ />
1165
+ <CaloProductTile
1166
+ image={mealImage}
1167
+ name="Meal title goes here"
1168
+ calories="123 kcal"
1169
+ price="2.5 BHD"
1170
+ badges={[{ label: "Label", variant: "neutral" }]}
1171
+ metadataLayout="inline"
1172
+ />
1173
+ <CaloStack gap="size12">
1174
+ <CaloCompactProductRowCard
1175
+ image={smoothieImage}
1176
+ title="The Brain Wizard Bar"
1177
+ meta="150 kcal"
1178
+ price="2.5"
1179
+ priceUnit="BHD"
1180
+ action="add"
1181
+ onAddPress={() => setMarketQuantity(1)}
1182
+ />
1183
+ <CaloCompactProductRowCard
1184
+ image={smoothieImage}
1185
+ title="بار معالج الدماغ"
1186
+ meta="495 ك.كال"
1187
+ price="2.5"
1188
+ priceUnit="د.ب"
1189
+ action="stepper"
1190
+ quantity={Math.max(1, marketQuantity)}
1191
+ language="ar"
1192
+ onIncrement={() => setMarketQuantity((value) => value + 1)}
1193
+ onDecrement={() => setMarketQuantity((value) => Math.max(0, value - 1))}
1194
+ />
1195
+ </CaloStack>
1196
+ <CaloInline gap="size16" wrap>
1197
+ <CaloMarketProductCard
1198
+ image={mealImage}
1199
+ name="Maximum 2 lines of market product title"
1200
+ price="3.5 BHD"
1201
+ oldPrice="3.96 BHD"
1202
+ discountLabel="15% off"
1203
+ action={marketQuantity > 0 ? "stepper" : "add"}
1204
+ quantity={marketQuantity}
1205
+ onAddPress={() => setMarketQuantity(1)}
1206
+ onIncrement={() => setMarketQuantity((value) => value + 1)}
1207
+ onDecrement={() => setMarketQuantity((value) => Math.max(0, value - 1))}
1208
+ />
1209
+ <CaloCafeProductCard
1210
+ image={smoothieImage}
1211
+ name="Mango protein smoothie"
1212
+ calories="210 kcal"
1213
+ price="2.4 BHD"
1214
+ action="stepper"
1215
+ quantity={cafeQuantity}
1216
+ scrolled
1217
+ onIncrement={() => setCafeQuantity((value) => value + 1)}
1218
+ onDecrement={() => setCafeQuantity((value) => Math.max(0, value - 1))}
1219
+ />
1220
+ </CaloInline>
1221
+ <CaloSelectableMealTile
1222
+ image={mealImage}
1223
+ name="Chicken teriyaki rice bowl"
1224
+ subtitle="Lunch"
1225
+ calories="520 Cal"
1226
+ macros={macros}
1227
+ badges={[{ label: "High protein", variant: "brand" }]}
1228
+ selected={mealSelected}
1229
+ onPress={() => setMealSelected((selected) => !selected)}
1230
+ />
1231
+ <CaloMenuMealRow
1232
+ image={mealImage}
1233
+ name="Chicken teriyaki rice bowl"
1234
+ mealType="Lunch"
1235
+ calories="520 Cal"
1236
+ macros={macros}
1237
+ badges={[{ label: "New", variant: "outline" }]}
1238
+ imageBottomBadge={{ label: "Contains dairy", tone: "danger" }}
1239
+ favorite
1240
+ onFavoritePress={previewOnly}
1241
+ onSwapPress={previewOnly}
1242
+ onCustomizePress={previewOnly}
1243
+ />
1244
+ <CaloMenuMealRow
1245
+ image={smoothieImage}
1246
+ name="Mango protein smoothie"
1247
+ mealType="Snack"
1248
+ calories="210 Cal"
1249
+ macros={macros}
1250
+ mode="rate"
1251
+ selectedRatingId={rowRating}
1252
+ onRatingSelect={setRowRating}
1253
+ />
1254
+ <CaloSwapMealCard image={mealImage} name="Grilled chicken with saffron rice" mealType="Dinner" calories="610 Cal" macros={macros} state="default" onPress={previewOnly} onFavoritePress={previewOnly} />
1255
+ <CaloSwapMealCard image={mealImage} name="Grilled chicken with saffron rice" mealType="Dinner" calories="610 Cal" macros={macros} state="loading" />
1256
+ <CaloSwapMealCard image={mealImage} name="Grilled chicken with saffron rice" mealType="Dinner" calories="610 Cal" macros={macros} state="fetching" />
1257
+ <CaloSwapMealCard image={mealImage} name="Grilled chicken with saffron rice" mealType="Dinner" calories="610 Cal" macros={macros} state="selected" />
1258
+ <CaloMealRow
1259
+ image={mealImage}
1260
+ name="Grilled chicken with saffron rice"
1261
+ mealType="Dinner"
1262
+ calories="610 Cal"
1263
+ macros={macros}
1264
+ badges={[{ label: "Chef pick", variant: "outline" }]}
1265
+ warnings={[{ label: "Contains nuts", tone: "danger" }]}
1266
+ selected
1267
+ favorite
1268
+ actionLabel="Swap"
1269
+ onActionPress={previewOnly}
1270
+ onFavoritePress={previewOnly}
1271
+ />
1272
+ <CaloMealRatingCard
1273
+ image={mealImage}
1274
+ title="Chicken teriyaki rice bowl"
1275
+ description="Tell us how this meal landed so future menus learn your taste."
1276
+ badgeLabel="Lunch"
1277
+ selectedOptionId={cardRating}
1278
+ onOptionSelect={setCardRating}
1279
+ />
1280
+ <CaloCustomizationOptionCard
1281
+ image={ingredientPenneImage}
1282
+ quantityTag="2x"
1283
+ title="Penne"
1284
+ metadata={["235 kcal", "120g"]}
1285
+ price="(+ SAR 3)"
1286
+ warning="Contains gluten"
1287
+ detailsTitle="Ingredients"
1288
+ details={["Durum wheat semolina, water, ", { text: "gluten", tone: "danger" }, ", sea salt."]}
1289
+ macros={macros}
1290
+ defaultExpanded
1291
+ secondaryActionLabel="Swap sauce"
1292
+ selected={checkboxSelected}
1293
+ onPress={() => setCheckboxSelected((selected) => !selected)}
1294
+ onSecondaryActionPress={previewOnly}
1295
+ />
1296
+ <CaloCustomizationOptionCard
1297
+ image={ingredientPenneImage}
1298
+ title="Penne"
1299
+ metadata={["235 kcal", "120g"]}
1300
+ detailsTitle="Ingredients"
1301
+ details={["Durum wheat semolina, water, sea salt."]}
1302
+ macros={macros}
1303
+ selected={!checkboxSelected}
1304
+ onPress={() => setCheckboxSelected((selected) => !selected)}
1305
+ />
1306
+ <CaloCustomizationOptionCard
1307
+ image={ingredientPenneImage}
1308
+ title="بيني"
1309
+ metadata={["235 سعرة", "120غ"]}
1310
+ price="(+ 3 ر.س)"
1311
+ warning={{ label: "يحتوي على الغلوتين", tone: "danger" }}
1312
+ detailsTitle="المكونات"
1313
+ details={["سميد القمح القاسي، ماء، ", { text: "غلوتين", tone: "danger" }, "، ملح بحري."]}
1314
+ macros={[
1315
+ { label: "ب", value: "37غ", tone: "protein" },
1316
+ { label: "ك", value: "42غ", tone: "carbs" },
1317
+ { label: "د", value: "14غ", tone: "fat" }
1318
+ ]}
1319
+ defaultExpanded
1320
+ secondaryActionLabel="بدل الصوص"
1321
+ selected
1322
+ language="ar"
1323
+ onPress={previewOnly}
1324
+ onSecondaryActionPress={previewOnly}
1325
+ />
1326
+ <CaloCustomizationOptionCard title="Choose spicy sauce" subtitle="One sauce per meal" price="Free" selectionType="radio" selected={radioSelected === "spicy"} onPress={() => setRadioSelected("spicy")} />
1327
+ <CaloCustomizationOptionCard image={ingredientPenneImage} title="Unavailable pasta" metadata={["235 kcal", "120g"]} selected={false} disabled onPress={previewOnly} />
1328
+ </GallerySection>
1329
+ ) : null}
1330
+
1331
+ {filterVisible("commerce") ? (
1332
+ <GallerySection title="Cart Items" label="Cafe + Market">
1333
+ <VariantGroup title="Cart item variants">
1334
+ <CaloCartItemCard
1335
+ family="cafe"
1336
+ image={mealImage}
1337
+ name="Chicken katsu curry"
1338
+ details="Extra sauce, no sesame"
1339
+ price="3.5 BHD"
1340
+ oldPrice="3.96 BHD"
1341
+ quantity={stepperValue}
1342
+ onEditPress={previewOnly}
1343
+ onIncrement={() => setStepperValue((value) => value + 1)}
1344
+ onDecrement={() => setStepperValue((value) => Math.max(0, value - 1))}
1345
+ />
1346
+ <CaloDivider />
1347
+ <CaloCartItemCard family="market" image={marketImage} name="Organic salad bowl" price="2.9 BHD" quantity={1} onIncrement={previewOnly} onDecrement={previewOnly} />
1348
+ <CaloDivider />
1349
+ <CaloCartItemCard family="cafe" variant="loyalty" image={smoothieImage} name="Reward smoothie" details="Club reward item" price="Free" quantity={1} onEditPress={previewOnly} onIncrement={previewOnly} onDecrement={previewOnly} />
1350
+ <CaloDivider />
1351
+ <CaloCartItemCard family="cafe" variant="unavailable" image={mealImage} name="Breakfast egg wrap" price="2.1 BHD" unavailableLabel="Not available at this branch" onRemovePress={previewOnly} />
1352
+ </VariantGroup>
1353
+ </GallerySection>
1354
+ ) : null}
1355
+
1356
+ {/* ===== Web / marketing components (Healthy UI "Web Components") ===== */}
1357
+ {filterVisible("marketing") ? (
1358
+ <GallerySection title="FAQ" label="Marketing">
1359
+ <CaloFaqAccordion
1360
+ expandMode="single"
1361
+ defaultOpenIds={["pause"]}
1362
+ items={[
1363
+ { id: "pause", question: "How do I pause my subscription?", answer: "To put your subscription on hold, sign in to your account, go to 'Account Settings', and choose 'Pause Subscription'. You can set the duration of the pause according to your needs." },
1364
+ { id: "delivery", question: "When will my meals arrive?", answer: "Your meals are delivered fresh before 7 AM on the days you choose." },
1365
+ { id: "swap", question: "Can I swap a meal I don't like?", answer: "Yes — open the menu for that day and pick any alternative before the cut-off time." }
1366
+ ]}
1367
+ />
1368
+ <CaloFaqAccordion
1369
+ expandMode="multiple"
1370
+ size="M"
1371
+ language="ar"
1372
+ defaultOpenIds={["pause"]}
1373
+ items={[
1374
+ { id: "pause", question: "كيف يمكنني إيقاف اشتراكي مؤقتًا؟", answer: 'لإيقاف اشتراكك مؤقتًا، سجّل الدخول إلى حسابك، ثم انتقل إلى "إعدادات الحساب"، واختر "إيقاف الاشتراك مؤقتًا".' },
1375
+ { id: "delivery", question: "متى ستصل وجباتي؟", answer: "تصلك وجباتك طازجة قبل الساعة 7 صباحًا في الأيام التي تختارها." }
1376
+ ]}
1377
+ />
1378
+ </GallerySection>
1379
+ ) : null}
1380
+
1381
+ {filterVisible("marketing") ? (
1382
+ <GallerySection title="Marketing Cards" label="Marketing">
1383
+ <CaloInline gap="size16" wrap>
1384
+ <CaloTestimonialCard
1385
+ avatar={{ uri: "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=200" }}
1386
+ name="Safa Ebrahim"
1387
+ subtitle="Calo subscription"
1388
+ rating={5}
1389
+ quote="I've been subscribed to Calo for over a year and a half now, and I can't imagine being without it! I reached my goal weight, but most of all, I love the flexibility and the outstanding customer service."
1390
+ />
1391
+ <CaloSupportPromptCard
1392
+ title="Got more questions?"
1393
+ description="Start a live chat with us or fill out the form. We're here to help!"
1394
+ avatars={[
1395
+ { uri: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120" },
1396
+ { uri: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=120" },
1397
+ { uri: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120" }
1398
+ ]}
1399
+ action={{ label: "Chat with us", onPress: previewOnly }}
1400
+ />
1401
+ <CaloUgcCard
1402
+ image={{ uri: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400" }}
1403
+ avatar={{ uri: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120" }}
1404
+ handle="@amaal"
1405
+ />
1406
+ <CaloFeatureCard
1407
+ image={{ uri: "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?w=600" }}
1408
+ title="Calories and macros that match your goals"
1409
+ description="Eat with confidence knowing every meal is cooked and portioned to your goals."
1410
+ />
1411
+ </CaloInline>
1412
+ </GallerySection>
1413
+ ) : null}
1414
+
1415
+ {filterVisible("marketing") ? (
1416
+ <GallerySection title="Partner Cards" label="Marketing">
1417
+ <CaloStack gap="size16">
1418
+ <CaloPartnerCard
1419
+ logo={<CaloText role="heading4">Fitness First</CaloText>}
1420
+ locations={["Deira City Center", "Bur Juman Centre", "DIFC"]}
1421
+ perks={["3 branches across Dubai", "Mixed gym & ladies gym", "Personal trainers & free classes"]}
1422
+ />
1423
+ <CaloPartnerCard
1424
+ language="ar"
1425
+ logo={<CaloText role="heading4">Fitness First</CaloText>}
1426
+ locations={["سيتي سنتر ديرة", "مول بر جمان", "مركز المالي"]}
1427
+ perks={["ثلاثة فروع في دبي", "صالة رياضية مختلطة وصالة رياضية للسيدات", "مدربون شخصيون ودروس مجانية"]}
1428
+ />
1429
+ </CaloStack>
1430
+ </GallerySection>
1431
+ ) : null}
1432
+
1433
+ {filterVisible("food") ? (
1434
+ <GallerySection title="Plan Cards" label="Food">
1435
+ <CaloPlanCardGrid>
1436
+ <CaloPlanCard type="balanced" selected onSelect={previewOnly} onLearnMore={previewOnly} />
1437
+ <CaloPlanCard type="highProtein" onSelect={previewOnly} onLearnMore={previewOnly} />
1438
+ <CaloPlanCard type="chefPicks" onSelect={previewOnly} onLearnMore={previewOnly} />
1439
+ <CaloPlanCard type="customMacros" onSelect={previewOnly} onLearnMore={previewOnly} />
1440
+ </CaloPlanCardGrid>
1441
+ </GallerySection>
1442
+ ) : null}
1443
+
1444
+ {filterVisible("selection") ? (
1445
+ <GallerySection title="Quantity Row" label="Steppers">
1446
+ <CaloStack gap="size8">
1447
+ <CaloQuantityRow label="Meals per day" value={webMeals} min={1} max={6} onIncrement={() => setWebMeals((v) => Math.min(6, v + 1))} onDecrement={() => setWebMeals((v) => Math.max(1, v - 1))} />
1448
+ <CaloQuantityRow label="Protein boost" subLabel="+12g protein per meal" value={1} max={3} error="Limited to 3 per plan" onIncrement={previewOnly} onDecrement={previewOnly} />
1449
+ <CaloQuantityRow label="وجبات في اليوم" subLabel="عدد الوجبات لكل يوم" value={webMeals} min={1} max={6} language="ar" onIncrement={() => setWebMeals((v) => Math.min(6, v + 1))} onDecrement={() => setWebMeals((v) => Math.max(1, v - 1))} />
1450
+ </CaloStack>
1451
+ </GallerySection>
1452
+ ) : null}
1453
+
1454
+ {filterVisible("navigation") ? (
1455
+ <GallerySection title="Pagination" label="Navigation">
1456
+ <CaloStack gap="size24">
1457
+ <CaloPagination page={paginationPage} pageCount={7} onChange={setPaginationPage} />
1458
+ <CaloPagination page={paginationPage} pageCount={7} size="small" onChange={setPaginationPage} />
1459
+ <CaloPagination page={paginationPage} pageCount={20} siblingCount={1} showPrevNext onChange={setPaginationPage} />
1460
+ <CaloPagination page={paginationPage} pageCount={7} language="ar" onChange={setPaginationPage} />
1461
+ </CaloStack>
1462
+ </GallerySection>
1463
+ ) : null}
1464
+
1465
+ {filterVisible("food") ? (
1466
+ <GallerySection title="Meal Feature Cards" label="Food">
1467
+ <CaloInline gap="size16" wrap>
1468
+ <CaloMealMacroCard
1469
+ image={{ uri: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=600" }}
1470
+ visual="avocado"
1471
+ title="Low carb"
1472
+ macros={[
1473
+ { label: "Protein", value: "40%", tone: "protein" },
1474
+ { label: "Carbs", value: "20%", tone: "carbs" },
1475
+ { label: "Fat", value: "40%", tone: "fat" }
1476
+ ]}
1477
+ />
1478
+ <CaloMealMacroCard
1479
+ image={{ uri: "https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?w=600" }}
1480
+ visual="meat"
1481
+ title="High Protein Plan"
1482
+ macros={[
1483
+ { label: "Protein", value: "50%", tone: "protein" },
1484
+ { label: "Carbs", value: "25%", tone: "carbs" },
1485
+ { label: "Fat", value: "25%", tone: "fat" }
1486
+ ]}
1487
+ />
1488
+ </CaloInline>
1489
+ <CaloInline gap="size16" wrap>
1490
+ <CaloDishCard
1491
+ image={{ uri: "https://images.unsplash.com/photo-1467003909585-2f8a72700288?w=400" }}
1492
+ calories="646 kcal"
1493
+ isNew
1494
+ title="Spiced Cajun Salmon Salad"
1495
+ description="Description up to 2 lines talking about the food"
1496
+ macros={[
1497
+ { label: "Pro", value: "20g", tone: "protein" },
1498
+ { label: "Carbs", value: "19g", tone: "carbs" },
1499
+ { label: "Fat", value: "19g", tone: "fat" }
1500
+ ]}
1501
+ />
1502
+ <CaloDishCard
1503
+ image={{ uri: "https://images.unsplash.com/photo-1455619452474-d2be8b1e70cd?w=400" }}
1504
+ calories="646 kcal"
1505
+ title="Asian Beef Rice Bowl"
1506
+ detailed={false}
1507
+ />
1508
+ </CaloInline>
1509
+ </GallerySection>
1510
+ ) : null}
1511
+
1512
+ {filterVisible("commerce") ? (
1513
+ <GallerySection title="Checkout Summary" label="Commerce">
1514
+ <CaloInline gap="size16" wrap>
1515
+ <CaloCheckoutSummaryCard
1516
+ subtitle="Balanced, 5 Meals, 5 Days per week"
1517
+ visual={{ uri: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400" }}
1518
+ promoValue=""
1519
+ onPromoChangeText={previewOnly}
1520
+ promoState="default"
1521
+ onPromoApply={previewOnly}
1522
+ lineItems={[
1523
+ { label: "Plan price", value: "2,200", currency: "SAR" },
1524
+ { label: "Delivery fee", value: "FREE", tone: "positive" },
1525
+ { label: "VAT(5%)", value: "115", currency: "SAR", info: true }
1526
+ ]}
1527
+ total={{ label: "Total", value: "2,085", currency: "SAR" }}
1528
+ checkoutVariant="default"
1529
+ onCheckout={previewOnly}
1530
+ />
1531
+ <CaloCheckoutSummaryCard
1532
+ subtitle="Balanced, 5 Meals, 5 Days per week"
1533
+ visual={{ uri: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400" }}
1534
+ promoValue="CALO2025"
1535
+ promoState="applied"
1536
+ onPromoRemove={previewOnly}
1537
+ lineItems={[
1538
+ { label: "Plan price", value: "2,200", currency: "SAR" },
1539
+ { label: "Delivery fee", value: "FREE", tone: "positive" },
1540
+ { label: "Discount", value: "115", currency: "SAR", tone: "positive", negative: true },
1541
+ { label: "VAT(5%)", value: "115", currency: "SAR", info: true }
1542
+ ]}
1543
+ total={{ label: "Total", value: "2,085", currency: "SAR" }}
1544
+ checkoutVariant="applePay"
1545
+ onApplePay={previewOnly}
1546
+ />
1547
+ <CaloCheckoutSummaryCard
1548
+ subtitle="Balanced, 5 Meals, 5 Days per week"
1549
+ visual={{ uri: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400" }}
1550
+ promoValue="CALO2025"
1551
+ promoState="error"
1552
+ onPromoChangeText={previewOnly}
1553
+ onPromoApply={previewOnly}
1554
+ promoErrorLabel="This promo code is not valid for this plan."
1555
+ lineItems={[
1556
+ { label: "Plan price", value: "2,200", currency: "SAR" },
1557
+ { label: "Delivery fee", value: "FREE", tone: "positive" },
1558
+ { label: "VAT(5%)", value: "115", currency: "SAR", info: true }
1559
+ ]}
1560
+ total={{ label: "Total", value: "2,085", currency: "SAR" }}
1561
+ onCheckout={previewOnly}
1562
+ />
1563
+ </CaloInline>
1564
+ </GallerySection>
1565
+ ) : null}
1566
+ </CaloStack>
1567
+ );
1568
+ }
1569
+
1570
+ function GallerySection({ title, label, children }: { title: string; label: string; children: React.ReactNode }) {
1571
+ return (
1572
+ <CaloStack gap="size16">
1573
+ <CaloInline align="center" justify="between">
1574
+ <CaloText role="section">{title}</CaloText>
1575
+ <CaloBadge label={label} variant="neutral" />
1576
+ </CaloInline>
1577
+ <CaloStack gap="size28">{children}</CaloStack>
1578
+ </CaloStack>
1579
+ );
1580
+ }
1581
+
1582
+ export function ComponentGalleryFilters({
1583
+ selectedFilter,
1584
+ onFilterChange
1585
+ }: {
1586
+ selectedFilter: GalleryCategory;
1587
+ visibleSectionCount?: number;
1588
+ onFilterChange: (filter: GalleryCategory) => void;
1589
+ }) {
1590
+ return (
1591
+ <CaloStack gap="size8">
1592
+ <CaloInline align="center" gap="size8" wrap>
1593
+ {galleryFilters.map((filter) => (
1594
+ <CaloButton
1595
+ key={filter.id}
1596
+ label={filter.label}
1597
+ size="small"
1598
+ variant={selectedFilter === filter.id ? "secondary" : "utility"}
1599
+ onPress={() => onFilterChange(filter.id)}
1600
+ />
1601
+ ))}
1602
+ </CaloInline>
1603
+ </CaloStack>
1604
+ );
1605
+ }
1606
+
1607
+ function VariantGroup({ title, children }: { title: string; children: React.ReactNode }) {
1608
+ return (
1609
+ <CaloStack gap="size16">
1610
+ <CaloText role="paragraphMediumStrong">{title}</CaloText>
1611
+ {children}
1612
+ </CaloStack>
1613
+ );
1614
+ }
1615
+
1616
+ function ButtonSizeRow({ label, children }: { label: string; children: React.ReactNode }) {
1617
+ return (
1618
+ <CaloInline align="center" gap="size20" wrap>
1619
+ <CaloText role="paragraphMediumStrong">{label}</CaloText>
1620
+ <CaloInline align="center" gap="size10" wrap>
1621
+ {children}
1622
+ </CaloInline>
1623
+ </CaloInline>
1624
+ );
1625
+ }
1626
+
1627
+ function TypographySpecimen({
1628
+ roleName,
1629
+ role,
1630
+ sample,
1631
+ usage,
1632
+ language = "en"
1633
+ }: {
1634
+ roleName: string;
1635
+ role: React.ComponentProps<typeof CaloText>["role"];
1636
+ sample: string;
1637
+ usage: string;
1638
+ language?: React.ComponentProps<typeof CaloText>["language"];
1639
+ }) {
1640
+ return (
1641
+ <CaloInline align="center" justify="between" gap="size16" wrap>
1642
+ <CaloStack gap="size4">
1643
+ <CaloText role="paragraphMediumStrong">{roleName}</CaloText>
1644
+ <CaloText role="captionSmall" tone="muted">{usage}</CaloText>
1645
+ </CaloStack>
1646
+ <CaloText role={role} language={language}>{sample}</CaloText>
1647
+ </CaloInline>
1648
+ );
1649
+ }
1650
+
1651
+ function isHealthySansAvailable() {
1652
+ if (typeof document === "undefined") return false;
1653
+ const canvas = document.createElement("canvas");
1654
+ const context = canvas.getContext("2d");
1655
+ if (!context) return false;
1656
+
1657
+ const text = "وجبة الغداء";
1658
+ const quotedFamily = JSON.stringify("HealthySans-Bold");
1659
+ const measure = (font: string) => {
1660
+ context.font = font;
1661
+ return context.measureText(text).width;
1662
+ };
1663
+
1664
+ const healthySansWidth = measure(`700 24px ${quotedFamily}`);
1665
+ const serifWidth = measure("700 24px serif");
1666
+ const sansWidth = measure("700 24px sans-serif");
1667
+
1668
+ return Math.abs(healthySansWidth - serifWidth) > 1 && Math.abs(healthySansWidth - sansWidth) > 1;
1669
+ }
1670
+
1671
+ function SelectionPreview({ label, children }: { label: string; children: React.ReactNode }) {
1672
+ return (
1673
+ <CaloInline align="center" gap="size8">
1674
+ {children}
1675
+ <CaloText role="body">{label}</CaloText>
1676
+ </CaloInline>
1677
+ );
1678
+ }