Skip to content

Commit ba5b4ce

Browse files
committed
feat(theme): add font family support and improve theme handling
- Add fontFamily to typography styles in all preset themes - Update theme provider to handle light/dark variants when applying presets - Apply font family to header titles and button text - Fix ghost button text color to always be black - Clean up imports and formatting in button component
1 parent 528a149 commit ba5b4ce

6 files changed

Lines changed: 145 additions & 222 deletions

File tree

apps/example/app/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function HomeScreen() {
3333
fallbackText="XL"
3434
variant="primary"
3535
/>
36-
<Typography style={{ fontWeight: 'black' }} variant="h1">
36+
<Typography variant="h1" style={{ fontFamily: theme.typography.title.fontFamily }}>
3737
Welcome to RNC Theme
3838
</Typography>
3939

apps/example/app/ui/_layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default function UILayout() {
3636
},
3737
headerTitleStyle: {
3838
color: theme.colors.text,
39+
fontFamily: theme.typography.title.fontFamily,
3940
},
4041
headerBackButtonDisplayMode: 'minimal',
4142
headerLeft: () => (

libs/rnc-theme/src/lib/components/theme-manager/index.tsx

Lines changed: 27 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -343,15 +343,12 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
343343
} else {
344344
const themeConfig = getThemeConfig(preset);
345345
if (themeConfig) {
346-
// Generate both light and dark variants when applying
347-
const lightTheme = themeConfig(false);
348-
const darkTheme = themeConfig(true);
349-
350-
// Apply current theme variant and save the config for future mode switches
346+
// updateCustomTheme will now automatically generate both light and dark variants
347+
// when themeConfig is provided, ensuring both modes are saved
351348
updateCustomTheme(
352-
isDark ? darkTheme : lightTheme,
349+
themeConfig(isDark), // Current theme for immediate application
353350
preset,
354-
themeConfig
351+
themeConfig // This will trigger generation of both variants
355352
);
356353
}
357354
}
@@ -399,9 +396,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
399396
{showControls &&
400397
(showCards ? (
401398
<Card style={[styles.card, contentStyle]}>
402-
<CardHeader
403-
title={customization?.controlsTitle ?? 'Theme Controls'}
404-
/>
399+
<CardHeader title={customization?.controlsTitle ?? 'Theme Controls'} />
405400
<CardContent>
406401
<View style={styles.row}>
407402
<Typography
@@ -411,8 +406,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
411406
}}
412407
>
413408
{customization?.darkModeLabel ?? 'Dark Mode'}{' '}
414-
{isDarkModeDisabled &&
415-
(customization?.darkModeDisabledLabel ?? '(Disabled)')}
409+
{isDarkModeDisabled && (customization?.darkModeDisabledLabel ?? '(Disabled)')}
416410
</Typography>
417411
<Switcher
418412
value={isDark}
@@ -423,44 +417,27 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
423417

424418
{previewTheme && previewTheme !== appliedTheme ? (
425419
<View>
426-
<Button
427-
variant="primary"
428-
onPress={applySelectedTheme}
429-
style={styles.button}
430-
>
420+
<Button variant="primary" onPress={applySelectedTheme} style={styles.button}>
431421
<ButtonText>
432422
{customization?.applyButtonText ??
433423
`Apply ${
434-
selectedPreset.charAt(0).toUpperCase() +
435-
selectedPreset.slice(1)
424+
selectedPreset.charAt(0).toUpperCase() + selectedPreset.slice(1)
436425
} Theme`}
437426
</ButtonText>
438427
</Button>
439428

440-
<Button
441-
variant="outline"
442-
onPress={cancelPreview}
443-
style={styles.button}
444-
>
445-
<ButtonText>
446-
{customization?.cancelPreviewText ?? 'Cancel Preview'}
447-
</ButtonText>
429+
<Button variant="outline" onPress={cancelPreview} style={styles.button}>
430+
<ButtonText>{customization?.cancelPreviewText ?? 'Cancel Preview'}</ButtonText>
448431
</Button>
449432
</View>
450433
) : (
451-
<Button
452-
variant="primary"
453-
onPress={applySelectedTheme}
454-
style={styles.button}
455-
>
456-
<ButtonText>
434+
<Button variant="primary" onPress={applySelectedTheme} style={styles.button}>
435+
<ButtonText style={{ color: 'white' }}>
457436
{selectedPreset === 'default'
458-
? customization?.resetButtonText ??
459-
'Reset to Default Theme'
437+
? customization?.resetButtonText ?? 'Reset to Default Theme'
460438
: customization?.applyButtonText ??
461439
`Apply ${
462-
selectedPreset.charAt(0).toUpperCase() +
463-
selectedPreset.slice(1)
440+
selectedPreset.charAt(0).toUpperCase() + selectedPreset.slice(1)
464441
} Theme`}
465442
</ButtonText>
466443
</Button>
@@ -471,9 +448,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
471448
onPress={() => applyThemePreset('default')}
472449
style={styles.button}
473450
>
474-
<ButtonText>
475-
{customization?.resetButtonText ?? 'Reset Theme'}
476-
</ButtonText>
451+
<ButtonText>{customization?.resetButtonText ?? 'Reset Theme'}</ButtonText>
477452
</Button>
478453
</CardContent>
479454
</Card>
@@ -490,55 +465,34 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
490465
}}
491466
>
492467
{customization?.darkModeLabel ?? 'Dark Mode'}{' '}
493-
{isDarkModeDisabled &&
494-
(customization?.darkModeDisabledLabel ?? '(Disabled)')}
468+
{isDarkModeDisabled && (customization?.darkModeDisabledLabel ?? '(Disabled)')}
495469
</Typography>
496-
<Switcher
497-
value={isDark}
498-
onValueChange={toggleTheme}
499-
disabled={isDarkModeDisabled}
500-
/>
470+
<Switcher value={isDark} onValueChange={toggleTheme} disabled={isDarkModeDisabled} />
501471
</View>
502472

503473
{previewTheme && previewTheme !== appliedTheme ? (
504474
<View>
505-
<Button
506-
variant="primary"
507-
onPress={applySelectedTheme}
508-
style={styles.button}
509-
>
475+
<Button variant="primary" onPress={applySelectedTheme} style={styles.button}>
510476
<ButtonText>
511477
{customization?.applyButtonText ??
512478
`Apply ${
513-
selectedPreset.charAt(0).toUpperCase() +
514-
selectedPreset.slice(1)
479+
selectedPreset.charAt(0).toUpperCase() + selectedPreset.slice(1)
515480
} Theme`}
516481
</ButtonText>
517482
</Button>
518483

519-
<Button
520-
variant="outline"
521-
onPress={cancelPreview}
522-
style={styles.button}
523-
>
524-
<ButtonText>
525-
{customization?.cancelPreviewText ?? 'Cancel Preview'}
526-
</ButtonText>
484+
<Button variant="outline" onPress={cancelPreview} style={styles.button}>
485+
<ButtonText>{customization?.cancelPreviewText ?? 'Cancel Preview'}</ButtonText>
527486
</Button>
528487
</View>
529488
) : (
530-
<Button
531-
variant="primary"
532-
onPress={applySelectedTheme}
533-
style={styles.button}
534-
>
489+
<Button variant="primary" onPress={applySelectedTheme} style={styles.button}>
535490
<ButtonText>
536491
{selectedPreset === 'default'
537492
? customization?.resetButtonText ?? 'Reset to Default Theme'
538493
: customization?.applyButtonText ??
539494
`Apply ${
540-
selectedPreset.charAt(0).toUpperCase() +
541-
selectedPreset.slice(1)
495+
selectedPreset.charAt(0).toUpperCase() + selectedPreset.slice(1)
542496
} Theme`}
543497
</ButtonText>
544498
</Button>
@@ -549,9 +503,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
549503
onPress={() => applyThemePreset('default')}
550504
style={styles.button}
551505
>
552-
<ButtonText>
553-
{customization?.resetButtonText ?? 'Reset Theme'}
554-
</ButtonText>
506+
<ButtonText>{customization?.resetButtonText ?? 'Reset Theme'}</ButtonText>
555507
</Button>
556508
</View>
557509
))}
@@ -560,9 +512,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
560512
{showPresets &&
561513
(showCards ? (
562514
<Card style={[styles.card, contentStyle]}>
563-
<CardHeader
564-
title={customization?.presetsTitle ?? 'Theme Presets'}
565-
/>
515+
<CardHeader title={customization?.presetsTitle ?? 'Theme Presets'} />
566516
<CardContent>
567517
<View style={styles.presetGrid}>
568518
<Button
@@ -581,8 +531,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
581531
<ButtonText>
582532
{customization?.defaultThemeText ?? 'Default'}
583533
{appliedTheme === 'default' && selectedPreset !== 'default'}
584-
{selectedPreset === 'default' &&
585-
previewTheme !== appliedTheme}
534+
{selectedPreset === 'default' && previewTheme !== appliedTheme}
586535
</ButtonText>
587536
</Button>
588537
{themePresets.map(({ key, label }) => (
@@ -631,8 +580,7 @@ export const ThemeManager: React.FC<ThemeManagerProps> = ({
631580
<ButtonText>
632581
{customization?.defaultThemeText ?? 'Default'}
633582
{appliedTheme === 'default' && selectedPreset !== 'default'}
634-
{selectedPreset === 'default' &&
635-
previewTheme !== appliedTheme}
583+
{selectedPreset === 'default' && previewTheme !== appliedTheme}
636584
</ButtonText>
637585
</Button>
638586
{themePresets.map(({ key, label }) => (

libs/rnc-theme/src/lib/components/ui/button/index.tsx

Lines changed: 19 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import React, {
2-
useMemo,
3-
useRef,
4-
forwardRef,
5-
useImperativeHandle,
6-
useCallback,
7-
} from 'react';
1+
import React, { useMemo, useRef, forwardRef, useImperativeHandle, useCallback } from 'react';
82
import {
93
Pressable,
104
TouchableOpacity,
@@ -32,16 +26,11 @@ import { useThemedStyles } from '../../../hooks/useThemedStyles';
3226
import { resolveColor } from '../../../utils';
3327
import { Spinner } from '../spinner';
3428
import { Typography } from '../typography';
35-
import {
36-
BaseComponentProps,
37-
ComponentSize,
38-
ComponentVariant,
39-
} from '../../../types/ui';
29+
import { BaseComponentProps, ComponentSize, ComponentVariant } from '../../../types/ui';
4030

4131
// Animated components
4232
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
43-
const AnimatedTouchableOpacity =
44-
Animated.createAnimatedComponent(TouchableOpacity);
33+
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
4534

4635
type ButtonComponent = 'pressable' | 'touchable';
4736
type IconPosition = 'left' | 'right' | 'center';
@@ -244,6 +233,7 @@ const createButtonTextStyles = (theme: Theme) =>
244233
StyleSheet.create({
245234
base: {
246235
fontWeight: '600',
236+
fontFamily: theme.typography.body.fontFamily,
247237
textAlign: 'center',
248238
},
249239
loadingContainer: {
@@ -259,7 +249,7 @@ const createButtonTextStyles = (theme: Theme) =>
259249
secondary: { color: '#FFFFFF' },
260250
outline: { color: resolveColor(theme, 'primary', theme.colors.primary) },
261251
filled: { color: theme.colors.text },
262-
ghost: { color: resolveColor(theme, 'text', theme.colors.text) },
252+
ghost: { color: '#000000' }, // FIXED: Pastikan text ghost variant selalu hitam
263253
success: { color: '#FFFFFF' },
264254
error: { color: '#FFFFFF' },
265255
warning: { color: '#FFFFFF' },
@@ -355,18 +345,13 @@ const createScaleAnimation = (
355345
);
356346
};
357347

358-
const createShakeAnimation = (
359-
translateX: SharedValue<number>,
360-
config: ShakeConfig
361-
): void => {
348+
const createShakeAnimation = (translateX: SharedValue<number>, config: ShakeConfig): void => {
362349
'worklet';
363350
const { translateX: translateValues, duration } = config;
364351
const stepDuration = duration / translateValues.length;
365352

366353
translateX.value = withSequence(
367-
...translateValues.map((value) =>
368-
withTiming(value, { duration: stepDuration })
369-
)
354+
...translateValues.map((value) => withTiming(value, { duration: stepDuration }))
370355
);
371356
};
372357

@@ -402,14 +387,7 @@ const useButtonAnimation = (
402387
if (animationEnabled && !disabled && !loading) {
403388
scale.value = withSpring(pressAnimationScale, springConfig);
404389
}
405-
}, [
406-
animationEnabled,
407-
pressAnimationScale,
408-
springConfig,
409-
scale,
410-
disabled,
411-
loading,
412-
]);
390+
}, [animationEnabled, pressAnimationScale, springConfig, scale, disabled, loading]);
413391

414392
const handlePressOut = useCallback(() => {
415393
'worklet';
@@ -480,14 +458,13 @@ const Button = forwardRef<ButtonRef, ButtonProps>(
480458
[springConfig]
481459
);
482460

483-
const { animatedStyle, handlePressIn, handlePressOut, animate } =
484-
useButtonAnimation(
485-
animationEnabled,
486-
pressAnimationScale,
487-
mergedSpringConfig,
488-
disabled,
489-
loading
490-
);
461+
const { animatedStyle, handlePressIn, handlePressOut, animate } = useButtonAnimation(
462+
animationEnabled,
463+
pressAnimationScale,
464+
mergedSpringConfig,
465+
disabled,
466+
loading
467+
);
491468

492469
// FIXED: Removed opacity from baseStyle
493470
const baseStyle = useMemo(
@@ -507,15 +484,7 @@ const Button = forwardRef<ButtonRef, ButtonProps>(
507484
}),
508485
...(style as ViewStyle),
509486
}),
510-
[
511-
styles,
512-
variant,
513-
size,
514-
theme.components.borderRadius,
515-
borderRadius,
516-
fullWidth,
517-
style,
518-
]
487+
[styles, variant, size, theme.components.borderRadius, borderRadius, fullWidth, style]
519488
);
520489

521490
const isDisabled = disabled || loading;
@@ -563,16 +532,10 @@ const Button = forwardRef<ButtonRef, ButtonProps>(
563532
[handlePressOut, onPressOut]
564533
);
565534

566-
const combinedStyle = useMemo(
567-
() => [baseStyle, animatedStyle],
568-
[baseStyle, animatedStyle]
569-
);
535+
const combinedStyle = useMemo(() => [baseStyle, animatedStyle], [baseStyle, animatedStyle]);
570536

571537
if (component === 'touchable') {
572-
const touchableProps = props as Omit<
573-
TouchableOpacityProps,
574-
keyof BaseButtonProps | 'style'
575-
>;
538+
const touchableProps = props as Omit<TouchableOpacityProps, keyof BaseButtonProps | 'style'>;
576539
return (
577540
<AnimatedTouchableOpacity
578541
ref={buttonRef}
@@ -587,10 +550,7 @@ const Button = forwardRef<ButtonRef, ButtonProps>(
587550
);
588551
}
589552

590-
const pressableProps = props as Omit<
591-
PressableProps,
592-
keyof BaseButtonProps | 'style'
593-
>;
553+
const pressableProps = props as Omit<PressableProps, keyof BaseButtonProps | 'style'>;
594554
return (
595555
<AnimatedPressable
596556
ref={buttonRef}

0 commit comments

Comments
 (0)