Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1317b25
add tailwind styling setup
Simek Dec 11, 2025
ec79b52
variables and dark mode init support
Simek Dec 12, 2025
0e3d728
switch to Tailwind v3, add custom config, patch `twrnc`, further tweaks
Simek Dec 12, 2025
da19ad6
avoid patching `twrnc`
Simek Dec 12, 2025
18a24f1
further refactors and theme expansion, add future TODOs
Simek Dec 12, 2025
c07b5ac
further refactor, patch `twrnc` for user-select
Simek Dec 13, 2025
1caebeb
fix lint
Simek Dec 13, 2025
9dbeae2
update `twrnc`, remove no longer needed patch
Simek Dec 13, 2025
86999e4
basic fonts setup, further refactors
Simek Dec 14, 2025
f314604
another batch of components refactors
Simek Dec 16, 2025
3cadf58
next batch of components refactors
Simek Dec 16, 2025
6966e1e
twrnc bump, package author bug fix, further refactors
Simek Dec 17, 2025
ce6b9b0
next batch of refactors
Simek Dec 17, 2025
7717e72
chipping out components to refactor
Simek Dec 18, 2025
73eb16d
few more refactors
Simek Dec 18, 2025
defaf3d
few more components refactored + tweaks
Simek Dec 18, 2025
469480b
update lock file after rebase
Simek Dec 18, 2025
d4fbeb1
update `twrnc`, address few TODOs
Simek Dec 20, 2025
45ae00c
few more refactors
Simek Dec 20, 2025
b1a4c58
chipping down remiaing refactors
Simek Dec 22, 2025
07fc9be
next set of refactors, minor tweaks and fixes
Simek Dec 24, 2025
e9fac98
migrate package overview scene
Simek Dec 25, 2025
c86ed5f
finish initial refactor, cleanup styleguide
Simek Dec 25, 2025
50b756e
tweaks and fixes part 1
Simek Dec 27, 2025
f715165
Merge branch 'main' of https://github.com/react-native-community/dire…
Simek Dec 30, 2025
60d134c
update `twrnc`, fix lint after rebase
Simek Dec 30, 2025
9477fcd
refactor new component
Simek Dec 30, 2025
5bc0de7
fixes and cleanups
Simek Dec 31, 2025
d7eaa4a
colors tweaks and UI fixes
Simek Jan 1, 2026
bf1319b
simplify theme context, fix style warning
Simek Jan 1, 2026
64f349b
theme tweaks and simplifications
Simek Jan 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions bun.lock

Large diffs are not rendered by default.

129 changes: 29 additions & 100 deletions common/styleguide.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import * as HtmlElements from '@expo/html-elements';
import { type TextProps } from '@expo/html-elements/build/primitives/Text';
import Link from 'next/link';
import {
type ComponentType,
type CSSProperties,
type PropsWithChildren,
useContext,
useState,
} from 'react';
import { type ComponentType, type CSSProperties, type PropsWithChildren, useState } from 'react';
import {
StyleSheet,
type TextStyle,
Expand All @@ -17,93 +11,40 @@ import {
type ViewStyle,
} from 'react-native';

import CustomAppearanceContext from '../context/CustomAppearanceContext';

export const layout = {
maxWidth: 1200,
};
import tw from '~/util/tailwind';

export function useLayout() {
const { width } = useWindowDimensions();
return {
isSmallScreen: width < 800,
isBelowMaxWidth: width < layout.maxWidth,
isBelowMaxWidth: width < 1200,
};
}

export const colors = {
primary: '#61DAFB',
primaryLight: '#c1f4ff',
primaryDark: '#39BEE2',
primaryHover: '#61dafb16',
sky: '#C6EEFB',
powder: '#EEFAFE',
pewter: '#BEC8CB',
gray1: '#f9f9f9',
gray2: '#ececec',
gray3: '#CFCFD5',
gray4: '#828898',
gray5: '#505461',
gray6: '#24262e',
gray7: '#21232A',
black: '#242424',
white: '#ffffff',
secondary: '#afb1af',
warning: '#FBE679',
warningLight: '#FEF7D6',
warningDark: '#995e00',
error: '#ff5555',
success: '#4caf50',
};

export const darkColors = {
black: '#000',
background: '#19191f',
subHeader: '#14141a',
border: '#2a2e36',
veryDark: '#111114',
dark: '#14141a',
darkBright: '#1c1c21',
powder: '#262a36',
pewter: '#767C8E',
secondary: '#a2a7ab',
warningLight: '#2f2704',
warning: '#9a810c',
primaryDark: '#2e9ab8',
};

const baseTextStyles = {
color: colors.black,
marginVertical: 0,
fontWeight: '400' as const,
fontFamily: 'inherit',
};

const textStyles = StyleSheet.create({
h1: { ...baseTextStyles, fontSize: 57.25, fontWeight: '600' as const },
h2: { ...baseTextStyles, fontSize: 35.5, fontWeight: '600' as const },
h3: { ...baseTextStyles, fontSize: 26.5, fontWeight: '600' as const },
h4: { ...baseTextStyles, fontSize: 22 },
h5: { ...baseTextStyles, fontSize: 20 },
h6: { ...baseTextStyles, fontSize: 18 },
headline: { ...baseTextStyles, fontSize: 16, fontWeight: '500' as const },
p: { ...baseTextStyles, fontSize: 16 },
caption: { ...baseTextStyles, fontSize: 15, lineHeight: 22 },
label: { ...baseTextStyles, fontSize: 12, fontWeight: '500' as const },
h1: tw`text-[57.25px] font-semibold`,
h2: tw`text-[35.5px] font-semibold`,
h3: tw`text-[26.5px] font-semibold`,
h4: tw`text-[22px]`,
h5: tw`text-[20px]`,
h6: tw`text-[18px]`,
headline: tw`text-[16px] font-medium`,
p: tw`text-[16px]`,
caption: tw`text-[15px] leading-[22px]`,
label: tw`text-[12px] font-medium`,
});

type TextStyles = TextStyle | TextStyle[];

type CustomTextProps = TextProps &
PropsWithChildren<{
id?: string;
numberOfLines?: number;
}>;

function createTextComponent(Element: ComponentType<TextProps>, textStyle?: TextStyles) {
export function createTextComponent(
Element: ComponentType<TextProps>,
textStyle?: StyleProp<TextStyle>
) {
function TextComponent({ children, style, id, numberOfLines }: CustomTextProps) {
const { isDark } = useContext(CustomAppearanceContext);

const elementStyle = Element?.displayName
? StyleSheet.flatten(textStyles[Element.displayName as keyof typeof textStyles])
: undefined;
Expand All @@ -112,7 +53,12 @@ function createTextComponent(Element: ComponentType<TextProps>, textStyle?: Text
<Element
id={id}
numberOfLines={numberOfLines}
style={[elementStyle, textStyle, { color: isDark ? colors.white : colors.black }, style]}>
style={[
tw`font-sans font-normal my-0 text-black dark:text-white`,
elementStyle as StyleProp<TextStyle>,
textStyle,
style,
]}>
{children}
</Element>
);
Expand All @@ -139,15 +85,14 @@ type AProps = PropsWithChildren<{
target?: string;
href: string;
hoverStyle?: StyleProp<TextStyle>;
containerStyle?: CSSProperties | undefined;
containerStyle?: CSSProperties;
}>;

export function A({ href, target, children, style, hoverStyle, containerStyle, ...rest }: AProps) {
const { isDark } = useContext(CustomAppearanceContext);
const [isHovered, setIsHovered] = useState(false);

const linkStyles = getLinkStyles(isDark);
const linkHoverStyles = getLinkHoverStyles();
const linkStyles = tw`font-sans text-black underline decoration-pewter dark:text-white dark:decoration-palette-gray5`;
const linkHoverStyles = tw`decoration-primary-dark`;

if ((target === '_self' && !href.startsWith('#')) || href.startsWith('/')) {
const passedStyle = StyleSheet.flatten(style);
Expand All @@ -171,7 +116,7 @@ export function A({ href, target, children, style, hoverStyle, containerStyle, .
<span
onPointerEnter={() => setIsHovered(true)}
onPointerLeave={() => setIsHovered(false)}
style={{ display: 'contents', ...containerStyle }}>
style={{ ...tw`contents`, ...containerStyle }}>
<HtmlElements.A
{...rest}
href={href}
Expand All @@ -185,21 +130,6 @@ export function A({ href, target, children, style, hoverStyle, containerStyle, .
);
}

function getLinkStyles(isDark: boolean): TextStyle {
return {
color: isDark ? colors.white : colors.black,
textDecorationColor: isDark ? colors.gray5 : colors.pewter,
textDecorationLine: 'underline',
fontFamily: 'inherit',
};
}

function getLinkHoverStyles(): TextStyle {
return {
textDecorationColor: colors.primaryDark,
};
}

type HoverEffectProps = PropsWithChildren<{ style?: StyleProp<ViewStyle> }>;

export function HoverEffect({ children, style }: HoverEffectProps) {
Expand All @@ -209,10 +139,9 @@ export function HoverEffect({ children, style }: HoverEffectProps) {
return (
<View
style={[
// @ts-expect-error Transition is a valid web style property
{ transition: 'opacity 0.33s' },
isHovered && { opacity: 0.75 },
isActive && { opacity: 0.5 },
isHovered && tw`opacity-75`,
isActive && tw`opacity-50`,
style,
]}
onPointerEnter={() => setIsHovered(true)}
Expand Down
27 changes: 5 additions & 22 deletions components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { A } from '@expo/html-elements';
import { type PropsWithChildren } from 'react';
import {
StyleSheet,
type TextStyle,
Pressable,
type StyleProp,
type ViewStyle,
} from 'react-native';
import { type TextStyle, Pressable, type StyleProp, type ViewStyle } from 'react-native';

import { darkColors, HoverEffect, P } from '~/common/styleguide';
import { HoverEffect, P } from '~/common/styleguide';
import tw from '~/util/tailwind';

type Props = PropsWithChildren & {
href?: string;
Expand All @@ -29,10 +24,7 @@ export function Button({
}: Props) {
const isLink = !!href;
const buttonStyle = [
styles.container,
{
backgroundColor: darkColors.primaryDark,
},
tw`justify-center items-center rounded outline-offset-1 select-none bg-primary-darker dark:bg-primary-dark`,
style,
];

Expand All @@ -43,7 +35,7 @@ export function Button({
{isLink ? (
<A
href={href}
style={{ borderRadius: 4, fontFamily: 'inherit', fontSize: 'inherit' }}
style={tw`rounded font-sans`}
{...(openInNewTab ? { target: '_blank' } : {})}
{...rest}>
<Pressable focusable={false} style={buttonStyle} accessible={false}>
Expand All @@ -58,12 +50,3 @@ export function Button({
</HoverEffect>
);
}

const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
outlineOffset: 1,
},
});
38 changes: 9 additions & 29 deletions components/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,26 @@
import { useContext } from 'react';
import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
import { type StyleProp, View, type ViewStyle } from 'react-native';

import { colors, darkColors } from '~/common/styleguide';
import CustomAppearanceContext from '~/context/CustomAppearanceContext';
import tw from '~/util/tailwind';

import { Check } from './Icons';

type Props = {
style?: StyleProp<ViewStyle>;
value?: boolean;
color?: string;
};

export default function CheckBox({ style, value, color }: Props) {
const { isDark } = useContext(CustomAppearanceContext);

export default function CheckBox({ style, value }: Props) {
return (
<View
style={[
styles.container,
{
borderColor: value ? color : isDark ? darkColors.border : colors.gray4,
backgroundColor: value ? color : isDark ? darkColors.dark : colors.white,
},
tw`size-[18px] items-center justify-center border-2 rounded-sm mr-2`,
value
? tw`border-primary-dark bg-primary-dark`
: tw`border-palette-gray4 bg-white dark:border-default dark:bg-dark`,
{ transition: 'border-color .33s, background-color .33s' },
style,
]}>
{value ? (
<Check width={14} height={10} fill={isDark ? darkColors.veryDark : colors.white} />
) : null}
{value ? <Check width={14} height={10} style={tw`text-white dark:text-black`} /> : null}
</View>
);
}

const styles = StyleSheet.create({
container: {
height: 18,
width: 18,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderStyle: 'solid',
borderRadius: 2,
marginRight: 8,
},
});
Loading