Live video on the AT Protocol
1/**
2 * Theme atoms - Enhanced exports with pairify function for array-style syntax
3 * These provide direct access to static design tokens and support composition
4 */
5
6import { Platform } from "react-native";
7import {
8 animations,
9 borderRadius,
10 colors as rawColors,
11 shadows,
12 spacing,
13 touchTargets,
14 typography,
15} from "./tokens";
16
17// Type for style objects that can be spread
18type StyleValue = Record<string, any>;
19
20/**
21 * Pairify function - converts nested objects into key-value pairs that return style objects
22 * This allows for array-style syntax like style={[a.borders.green[300], a.shadows.xl]}
23 */
24function pairify<T extends Record<string, any>>(
25 obj: T,
26 styleKeyPrefix: string,
27): Record<keyof T, StyleValue> {
28 const result: Record<string, StyleValue> = {};
29
30 for (const [key, value] of Object.entries(obj)) {
31 if (typeof value === "object" && value !== null && !Array.isArray(value)) {
32 // For nested objects (like color scales), create another level
33 result[key] = {};
34 for (const [nestedKey, nestedValue] of Object.entries(value)) {
35 result[key][nestedKey] = { [styleKeyPrefix]: nestedValue };
36 }
37 } else {
38 // For simple values, create the style object directly
39 result[key] = { [styleKeyPrefix]: value };
40 }
41 }
42
43 return result as Record<keyof T, StyleValue>;
44}
45
46/**
47 * Create pairified style atoms for easy composition
48 */
49
50// Re-export static design tokens that don't change with theme
51export { animations, borderRadius, shadows, spacing, touchTargets };
52
53// Export raw color tokens for advanced use cases
54export const colors = rawColors;
55
56// Platform-aware typography helper
57export const getPlatformTypography = () => {
58 if (Platform.OS === "ios") {
59 return typography.ios;
60 } else if (Platform.OS === "android") {
61 return typography.android;
62 }
63 return typography.universal;
64};
65
66// Export all typography scales
67export const typographyAtoms = {
68 platform: getPlatformTypography(),
69 universal: typography.universal,
70 ios: typography.ios,
71 android: typography.android,
72};
73
74// Static icon sizes (colors are handled by theme)
75export const iconSizes = {
76 sm: 16,
77 md: 20,
78 lg: 24,
79 xl: 32,
80};
81
82// Common layout utilities
83export const layout = {
84 flex: {
85 center: {
86 justifyContent: "center" as const,
87 alignItems: "center" as const,
88 },
89 alignCenter: {
90 alignItems: "center" as const,
91 },
92 justifyCenter: {
93 justifyContent: "center" as const,
94 },
95 row: {
96 flexDirection: "row" as const,
97 },
98 column: {
99 flexDirection: "column" as const,
100 },
101 spaceBetween: {
102 justifyContent: "space-between" as const,
103 },
104 spaceAround: {
105 justifyContent: "space-around" as const,
106 },
107 spaceEvenly: {
108 justifyContent: "space-evenly" as const,
109 },
110 direction: pairify(
111 {
112 row: "row",
113 column: "column",
114 "row-reverse": "row-reverse",
115 "column-reverse": "column-reverse",
116 },
117 "flexDirection",
118 ),
119 justify: pairify(
120 {
121 start: "flex-start",
122 end: "flex-end",
123 center: "center",
124 between: "space-between",
125 around: "space-around",
126 evenly: "space-evenly",
127 },
128 "justifyContent",
129 ),
130 align: pairify(
131 {
132 start: "flex-start",
133 end: "flex-end",
134 center: "center",
135 stretch: "stretch",
136 baseline: "baseline",
137 },
138 "alignItems",
139 ),
140 wrap: pairify(
141 {
142 wrap: "wrap",
143 nowrap: "nowrap",
144 reverse: "wrap-reverse",
145 },
146 "flexWrap",
147 ),
148 },
149 position: pairify(
150 {
151 absolute: "absolute",
152 relative: "relative",
153 static: "static",
154 },
155 "position",
156 ),
157};
158
159// Enhanced border utilities with pairified colors and widths
160export const borders = {
161 width: pairify(
162 {
163 thin: 1,
164 medium: 2,
165 thick: 4,
166 },
167 "borderWidth",
168 ),
169
170 style: pairify(
171 {
172 solid: "solid",
173 dashed: "dashed",
174 dotted: "dotted",
175 },
176 "borderStyle",
177 ),
178
179 // Pairified color borders
180 color: pairify(rawColors, "borderColor"),
181
182 // Top border utilities
183 top: {
184 width: pairify(
185 {
186 thin: 1,
187 medium: 2,
188 thick: 4,
189 },
190 "borderTopWidth",
191 ),
192 color: pairify(rawColors, "borderTopColor"),
193 },
194
195 // Bottom border utilities
196 bottom: {
197 width: pairify(
198 {
199 thin: 1,
200 medium: 2,
201 thick: 4,
202 },
203 "borderBottomWidth",
204 ),
205 color: pairify(rawColors, "borderBottomColor"),
206 },
207
208 // Left border utilities
209 left: {
210 width: pairify(
211 {
212 thin: 1,
213 medium: 2,
214 thick: 4,
215 },
216 "borderLeftWidth",
217 ),
218 color: pairify(rawColors, "borderLeftColor"),
219 },
220
221 // Right border utilities
222 right: {
223 width: pairify(
224 {
225 thin: 1,
226 medium: 2,
227 thick: 4,
228 },
229 "borderRightWidth",
230 ),
231 color: pairify(rawColors, "borderRightColor"),
232 },
233};
234
235// Pairified spacing utilities
236export const spacingAtoms = {
237 margin: pairify(spacing, "margin"),
238 marginTop: pairify(spacing, "marginTop"),
239 marginRight: pairify(spacing, "marginRight"),
240 marginBottom: pairify(spacing, "marginBottom"),
241 marginLeft: pairify(spacing, "marginLeft"),
242 marginHorizontal: pairify(spacing, "marginHorizontal"),
243 marginVertical: pairify(spacing, "marginVertical"),
244
245 padding: pairify(spacing, "padding"),
246 paddingTop: pairify(spacing, "paddingTop"),
247 paddingRight: pairify(spacing, "paddingRight"),
248 paddingBottom: pairify(spacing, "paddingBottom"),
249 paddingLeft: pairify(spacing, "paddingLeft"),
250 paddingHorizontal: pairify(spacing, "paddingHorizontal"),
251 paddingVertical: pairify(spacing, "paddingVertical"),
252};
253
254// Pairified border radius utilities
255export const radiusAtoms = {
256 all: pairify(borderRadius, "borderRadius"),
257 top: pairify(borderRadius, "borderTopLeftRadius"),
258 topRight: pairify(borderRadius, "borderTopRightRadius"),
259 bottom: pairify(borderRadius, "borderBottomLeftRadius"),
260 bottomRight: pairify(borderRadius, "borderBottomRightRadius"),
261 left: pairify(borderRadius, "borderTopLeftRadius"),
262 right: pairify(borderRadius, "borderTopRightRadius"),
263};
264
265// Background color utilities
266export const backgrounds = pairify(rawColors, "backgroundColor");
267
268// Text color utilities
269export const textColors = pairify(rawColors, "color");
270
271// Percentage-based sizes
272const percentageSizes = {
273 "10": "10%",
274 "20": "20%",
275 "25": "25%",
276 "30": "30%",
277 "33": "33.333333%",
278 "40": "40%",
279 "50": "50%",
280 "60": "60%",
281 "66": "66.666667%",
282 "70": "70%",
283 "75": "75%",
284 "80": "80%",
285 "90": "90%",
286 "100": "100%",
287} as const;
288
289// Size utilities (width and height)
290export const sizes = {
291 width: {
292 ...pairify(spacing, "width"),
293 percent: pairify(percentageSizes, "width"),
294 },
295 height: {
296 ...pairify(spacing, "height"),
297 percent: pairify(percentageSizes, "height"),
298 },
299 minWidth: {
300 ...pairify(spacing, "minWidth"),
301 percent: pairify(percentageSizes, "minWidth"),
302 },
303 minHeight: {
304 ...pairify(spacing, "minHeight"),
305 percent: pairify(percentageSizes, "minHeight"),
306 },
307 maxWidth: {
308 ...pairify(spacing, "maxWidth"),
309 percent: pairify(percentageSizes, "maxWidth"),
310 },
311 maxHeight: {
312 ...pairify(spacing, "maxHeight"),
313 percent: pairify(percentageSizes, "maxHeight"),
314 },
315};
316
317// Flex utilities
318export const flex = {
319 values: pairify(
320 {
321 0: 0,
322 1: 1,
323 2: 2,
324 3: 3,
325 4: 4,
326 5: 5,
327 },
328 "flex",
329 ),
330
331 grow: pairify(
332 {
333 0: 0,
334 1: 1,
335 },
336 "flexGrow",
337 ),
338
339 shrink: pairify(
340 {
341 0: 0,
342 1: 1,
343 },
344 "flexShrink",
345 ),
346
347 basis: {
348 ...pairify(spacing, "flexBasis"),
349 ...pairify(percentageSizes, "flexBasis"),
350 auto: { flexBasis: "auto" },
351 },
352};
353
354// Opacity utilities
355export const opacity = pairify(
356 {
357 0: 0,
358 5: 0.05,
359 10: 0.1,
360 20: 0.2,
361 25: 0.25,
362 30: 0.3,
363 40: 0.4,
364 50: 0.5,
365 60: 0.6,
366 70: 0.7,
367 75: 0.75,
368 80: 0.8,
369 90: 0.9,
370 95: 0.95,
371 100: 1,
372 },
373 "opacity",
374);
375
376// Z-index utilities
377export const zIndex = pairify(
378 {
379 0: 0,
380 10: 10,
381 20: 20,
382 30: 30,
383 40: 40,
384 50: 50,
385 auto: "auto",
386 },
387 "zIndex",
388);
389
390// Overflow utilities
391export const overflow = pairify(
392 {
393 visible: "visible",
394 hidden: "hidden",
395 scroll: "scroll",
396 },
397 "overflow",
398);
399
400// Text alignment utilities
401export const textAlign = pairify(
402 {
403 left: "left",
404 center: "center",
405 right: "right",
406 justify: "justify",
407 auto: "auto",
408 },
409 "textAlign",
410);
411
412// Font weight utilities
413export const fontWeight = pairify(
414 {
415 thin: "100",
416 extralight: "200",
417 light: "300",
418 normal: "400",
419 medium: "500",
420 semibold: "600",
421 bold: "700",
422 extrabold: "800",
423 black: "900",
424 },
425 "fontWeight",
426);
427
428// Font size utilities (separate from typography for quick access)
429export const fontSize = pairify(
430 {
431 xs: 12,
432 sm: 14,
433 base: 16,
434 lg: 18,
435 xl: 20,
436 "2xl": 24,
437 "3xl": 30,
438 "4xl": 36,
439 "5xl": 48,
440 "6xl": 60,
441 "7xl": 72,
442 "8xl": 96,
443 "9xl": 128,
444 },
445 "fontSize",
446);
447
448// Line height utilities
449export const lineHeight = pairify(
450 {
451 none: 1,
452 tight: 1.25,
453 snug: 1.375,
454 normal: 1.5,
455 relaxed: 1.625,
456 loose: 2,
457 3: 12,
458 4: 16,
459 5: 20,
460 6: 24,
461 7: 28,
462 8: 32,
463 9: 36,
464 10: 40,
465 },
466 "lineHeight",
467);
468
469// Letter spacing utilities
470export const letterSpacing = pairify(
471 {
472 tighter: -0.5,
473 tight: -0.25,
474 normal: 0,
475 wide: 0.25,
476 wider: 0.5,
477 widest: 1,
478 },
479 "letterSpacing",
480);
481
482// Text transform utilities
483export const textTransform = pairify(
484 {
485 uppercase: "uppercase",
486 lowercase: "lowercase",
487 capitalize: "capitalize",
488 none: "none",
489 },
490 "textTransform",
491);
492
493// Text decoration utilities
494export const textDecoration = pairify(
495 {
496 none: "none",
497 underline: "underline",
498 lineThrough: "line-through",
499 underlineLineThrough: "underline line-through",
500 },
501 "textDecorationLine",
502);
503
504// Text align vertical utilities (React Native specific)
505export const textAlignVertical = pairify(
506 {
507 auto: "auto",
508 top: "top",
509 bottom: "bottom",
510 center: "center",
511 },
512 "textAlignVertical",
513);
514
515// Transform utilities
516export const transforms = {
517 rotate: pairify(
518 {
519 0: 0,
520 1: 1,
521 2: 2,
522 3: 3,
523 6: 6,
524 12: 12,
525 45: 45,
526 90: 90,
527 180: 180,
528 270: 270,
529 },
530 "rotate",
531 ),
532
533 scale: pairify(
534 {
535 0: 0,
536 50: 0.5,
537 75: 0.75,
538 90: 0.9,
539 95: 0.95,
540 100: 1,
541 105: 1.05,
542 110: 1.1,
543 125: 1.25,
544 150: 1.5,
545 200: 2,
546 },
547 "scale",
548 ),
549
550 scaleX: pairify(
551 {
552 0: 0,
553 50: 0.5,
554 75: 0.75,
555 90: 0.9,
556 95: 0.95,
557 100: 1,
558 105: 1.05,
559 110: 1.1,
560 125: 1.25,
561 150: 1.5,
562 200: 2,
563 },
564 "scaleX",
565 ),
566
567 scaleY: pairify(
568 {
569 0: 0,
570 50: 0.5,
571 75: 0.75,
572 90: 0.9,
573 95: 0.95,
574 100: 1,
575 105: 1.05,
576 110: 1.1,
577 125: 1.25,
578 150: 1.5,
579 200: 2,
580 },
581 "scaleY",
582 ),
583
584 translateX: pairify(spacing, "translateX"),
585 translateY: pairify(spacing, "translateY"),
586};
587
588// Absolute positioning utilities
589export const position = {
590 top: pairify(spacing, "top"),
591 right: pairify(spacing, "right"),
592 bottom: pairify(spacing, "bottom"),
593 left: pairify(spacing, "left"),
594
595 // Common position combinations
596 topLeft: (top: number, left: number) => ({
597 position: "absolute" as const,
598 top,
599 left,
600 }),
601 topRight: (top: number, right: number) => ({
602 position: "absolute" as const,
603 top,
604 right,
605 }),
606 bottomLeft: (bottom: number, left: number) => ({
607 position: "absolute" as const,
608 bottom,
609 left,
610 }),
611 bottomRight: (bottom: number, right: number) => ({
612 position: "absolute" as const,
613 bottom,
614 right,
615 }),
616
617 // Percentage-based positioning
618 percent: {
619 top: pairify(
620 {
621 0: "0%",
622 25: "25%",
623 50: "50%",
624 75: "75%",
625 100: "100%",
626 },
627 "top",
628 ),
629 right: pairify(
630 {
631 0: "0%",
632 25: "25%",
633 50: "50%",
634 75: "75%",
635 100: "100%",
636 },
637 "right",
638 ),
639 bottom: pairify(
640 {
641 0: "0%",
642 25: "25%",
643 50: "50%",
644 75: "75%",
645 100: "100%",
646 },
647 "bottom",
648 ),
649 left: pairify(
650 {
651 0: "0%",
652 25: "25%",
653 50: "50%",
654 75: "75%",
655 100: "100%",
656 },
657 "left",
658 ),
659 },
660};
661
662// Aspect ratio utilities (React Native 0.71+)
663export const aspectRatio = pairify(
664 {
665 square: 1,
666 video: 16 / 9,
667 photo: 4 / 3,
668 portrait: 3 / 4,
669 wide: 21 / 9,
670 ultrawide: 32 / 9,
671 "1/1": 1,
672 "3/2": 3 / 2,
673 "4/3": 4 / 3,
674 "16/9": 16 / 9,
675 "21/9": 21 / 9,
676 },
677 "aspectRatio",
678);
679
680// Gap utilities (React Native 0.71+)
681export const gap = {
682 row: pairify(spacing, "rowGap"),
683 column: pairify(spacing, "columnGap"),
684 all: pairify(spacing, "gap"),
685};
686
687// Common layout patterns
688export const layouts = {
689 // Full screen
690 fullScreen: {
691 position: "absolute" as const,
692 top: 0,
693 left: 0,
694 right: 0,
695 bottom: 0,
696 },
697
698 // Centered content
699 centered: {
700 flex: 1,
701 justifyContent: "center" as const,
702 alignItems: "center" as const,
703 },
704
705 // Centered modal/overlay
706 overlay: {
707 position: "absolute" as const,
708 top: 0,
709 left: 0,
710 right: 0,
711 bottom: 0,
712 justifyContent: "center" as const,
713 alignItems: "center" as const,
714 backgroundColor: "rgba(0, 0, 0, 0.5)",
715 },
716
717 // Safe area friendly
718 safeContainer: {
719 flex: 1,
720 paddingTop: Platform.OS === "ios" ? 44 : 0, // Status bar height
721 },
722
723 // Row with space between
724 spaceBetweenRow: {
725 flexDirection: "row" as const,
726 justifyContent: "space-between" as const,
727 alignItems: "center" as const,
728 },
729
730 // Sticky header
731 stickyHeader: {
732 position: "absolute" as const,
733 top: 0,
734 left: 0,
735 right: 0,
736 zIndex: 10,
737 },
738
739 // Bottom sheet style
740 bottomSheet: {
741 position: "absolute" as const,
742 bottom: 0,
743 left: 0,
744 right: 0,
745 borderTopLeftRadius: 16,
746 borderTopRightRadius: 16,
747 },
748};
749
750// Export everything as a combined atoms object for convenience
751export const atoms = {
752 colors: rawColors,
753 spacing,
754 borderRadius,
755 radius: radiusAtoms,
756 typography: typographyAtoms,
757 shadows,
758 touchTargets,
759 animations,
760 iconSizes,
761 layout,
762 borders,
763 backgrounds,
764 textColors,
765 spacingAtoms,
766 sizes,
767 flex,
768 opacity,
769 zIndex,
770 overflow,
771 textAlign,
772 fontWeight,
773 fontSize,
774 lineHeight,
775 letterSpacing,
776 textTransform,
777 textDecoration,
778 textAlignVertical,
779 transforms,
780 position,
781 aspectRatio,
782 gap,
783 layouts,
784};
785
786// Convenient shorthand aliases
787export const a = atoms;
788export const bg = backgrounds;
789export const text = textColors;
790export const m = spacingAtoms.margin;
791export const mt = spacingAtoms.marginTop;
792export const mr = spacingAtoms.marginRight;
793export const mb = spacingAtoms.marginBottom;
794export const ml = spacingAtoms.marginLeft;
795export const mx = spacingAtoms.marginHorizontal;
796export const my = spacingAtoms.marginVertical;
797export const p = spacingAtoms.padding;
798export const pt = spacingAtoms.paddingTop;
799export const pr = spacingAtoms.paddingRight;
800export const pb = spacingAtoms.paddingBottom;
801export const pl = spacingAtoms.paddingLeft;
802export const px = spacingAtoms.paddingHorizontal;
803export const py = spacingAtoms.paddingVertical;
804export const w = sizes.width;
805export const h = sizes.height;
806export const r = radiusAtoms.all;
807export const top = position.top;
808export const right = position.right;
809export const bottom = position.bottom;
810export const left = position.left;
811export const rotate = transforms.rotate;
812export const scale = transforms.scale;
813export const translateX = transforms.translateX;
814export const translateY = transforms.translateY;