Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion mobile/src/components/SystemState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AppText } from '../design/components/Text';
import { Button } from '../design/components/Button';
import { enter } from '../design/motion';
import { palette } from '../design/tokens';
import { displayTitle } from '../layouts/displayTitle';

export interface SystemStateProps {
/** small line above the title, e.g. "VIC" */
Expand Down Expand Up @@ -88,7 +89,7 @@ const styles = StyleSheet.create((theme) => ({
},
body: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 24 },
copy: { alignItems: 'center', gap: 10 },
title: { fontSize: 38, lineHeight: 40, textAlign: 'center' },
title: { ...displayTitle(38), textAlign: 'center' },
description: { maxWidth: 320 },
center: { textAlign: 'center' },
}));
24 changes: 13 additions & 11 deletions mobile/src/components/auth/CodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ const CodeBox = ({
return (
<View style={styles.cell}>
<Animated.View pointerEvents="none" style={[styles.halo, ring.halo]} />
<Animated.View
style={[styles.box, digit ? styles.filled : null, error ? styles.error : null, ring.field]}
>
<Animated.View style={[styles.box, error ? styles.error : null, ring.field]}>
<Text maxFontSizeMultiplier={fontScaleCap.display} style={styles.digit}>
{digit ?? ''}
</Text>
Expand Down Expand Up @@ -114,36 +112,40 @@ CodeInput.displayName = 'CodeInput';

export default CodeInput;

const BOX_HEIGHT = 60;
// the field height of the handbook; the focus halo is the field's 5pt one
const BOX_HEIGHT = 56;
const HALO = 5;
const BOX_RADIUS = 14;

const styles = StyleSheet.create((theme) => ({
row: { flexDirection: 'row', justifyContent: 'center', gap: 8 },
// the six boxes share the width, so they fit a 320pt phone and fill a large one
cell: { flex: 1, maxWidth: 56 },
halo: {
position: 'absolute',
top: -4,
left: -4,
right: -4,
bottom: -4,
borderRadius: 18,
top: -HALO,
left: -HALO,
right: -HALO,
bottom: -HALO,
borderRadius: BOX_RADIUS + HALO,
backgroundColor: theme.colors.yellow,
},
box: {
height: BOX_HEIGHT,
borderRadius: 14,
borderRadius: BOX_RADIUS,
borderWidth: 2,
borderColor: 'transparent',
backgroundColor: theme.colors.surface,
alignItems: 'center',
justifyContent: 'center',
},
filled: { backgroundColor: theme.colors.card },
error: { backgroundColor: theme.colors.errorTint },
digit: {
fontFamily: theme.fontFamily.display,
fontSize: 28,
lineHeight: 34,
letterSpacing: -0.7,
includeFontPadding: false,
color: theme.colors.ink,
fontVariant: ['tabular-nums'],
},
Expand Down
3 changes: 2 additions & 1 deletion mobile/src/components/auth/SocialButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ const styles = StyleSheet.create((theme) => ({
paddingHorizontal: 18,
},
light: { backgroundColor: theme.colors.card },
outline: { borderWidth: 2, borderColor: 'rgba(255,255,255,0.7)' },
// the handbook's outline button (2pt, full color), in white on navy
outline: { borderWidth: 2, borderColor: theme.colors.white },
disabled: { opacity: 0.45 },
icon: { width: 24, alignItems: 'center' },
label: { flex: 1, textAlign: 'center' },
Expand Down
46 changes: 37 additions & 9 deletions mobile/src/components/onboarding/StoryScenes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';
import Animated, {
FadeIn,
useAnimatedStyle,
useReducedMotion,
useSharedValue,
Expand All @@ -11,9 +12,16 @@ import Animated, {
withTiming,
} from 'react-native-reanimated';
import { useTranslation } from 'react-i18next';
import { VText } from '../ui/VText';
import { VText as BaseText, VTextProps } from '../ui/VText';
import { Badge } from '../ui/Badge';
import { colors, fonts, motion } from '../../common/theme/tokens';
import { radius as dsRadius } from '../../design/tokens';
import { duration } from '../../design/motion';

// The scenes are pictures: the stage is one button with its own label, so their sample text is not
// read and does not grow with Dynamic Type (it would overflow the fixed cards); the story title
// under the stage grows instead.
const VText = (props: VTextProps) => <BaseText maxFontSizeMultiplier={1} {...props} />;

const EASE = motion.enter.easing;

Expand Down Expand Up @@ -44,7 +52,10 @@ const Fly = ({
}) => {
const reducedMotion = useReducedMotion();
return (
<Animated.View style={[styles.mini, style]} entering={reducedMotion ? undefined : flyIn(delay)}>
<Animated.View
style={[styles.mini, style]}
entering={reducedMotion ? FadeIn.duration(duration.reduced) : flyIn(delay)}
>
{children}
</Animated.View>
);
Expand Down Expand Up @@ -268,8 +279,16 @@ const HoursScene = () => {
<Swap
after={1300}
style={styles.swapBadge}
from={<Badge label={t('sample.pending')} tone="wait" />}
to={<Badge label={t('sample.approved')} tone="ok" />}
from={
<View style={styles.badgeEnd}>
<Badge label={t('sample.pending')} tone="wait" />
</View>
}
to={
<View style={styles.badgeEnd}>
<Badge label={t('sample.approved')} tone="ok" />
</View>
}
/>
</Fly>
</View>
Expand Down Expand Up @@ -321,7 +340,7 @@ export const StoryScenes = [OrganizationsScene, EventScene, HoursScene, Document
const styles = StyleSheet.create({
mini: {
backgroundColor: colors.white,
borderRadius: 16,
borderRadius: dsRadius.row,
shadowColor: '#000',
shadowOpacity: 0.35,
shadowRadius: 20,
Expand Down Expand Up @@ -355,8 +374,8 @@ const styles = StyleSheet.create({
cover: {
height: 150,
backgroundColor: colors.teal,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
borderTopLeftRadius: dsRadius.row,
borderTopRightRadius: dsRadius.row,
},
coverBadge: {
position: 'absolute',
Expand All @@ -375,11 +394,20 @@ const styles = StyleSheet.create({
sceneHours: { width: 290, gap: 12 },
hoursCard: { padding: 16, gap: 10 },
hoursTotal: { flexDirection: 'row', alignItems: 'baseline', gap: 6 },
bigNumber: { fontFamily: fonts.title, fontSize: 34, lineHeight: 38, color: colors.ink },
bigNumber: {
fontFamily: fonts.title,
fontSize: 34,
lineHeight: 38,
letterSpacing: -1.36,
fontVariant: ['tabular-nums'],
color: colors.ink,
},
bars: { flexDirection: 'row', alignItems: 'flex-end', gap: 8, height: 80 },
barSlot: { flex: 1, height: '100%', justifyContent: 'flex-end' },
bar: { width: '100%', borderTopLeftRadius: 6, borderTopRightRadius: 6, borderRadius: 3 },
swapBadge: { width: 104, height: 24, alignItems: 'flex-end' },
// the status pills are 28pt; both sit at the right edge of the row
swapBadge: { width: 124, height: 28 },
badgeEnd: { flex: 1, alignItems: 'flex-end', justifyContent: 'center' },
sceneDocs: { width: 250, height: 300 },
paper: { position: 'absolute', padding: 18, gap: 8 },
paperBack: {
Expand Down
18 changes: 18 additions & 0 deletions mobile/src/design/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ export const Button = ({
: { transform: [{ scale }] };
});
const pressFill = useAnimatedStyle(() => ({ opacity: pressed.value }));
// the yellow button's darker bottom edge (inset 0 -3 navy 12%) tightens to 1pt when pressed
const edgeRest = useAnimatedStyle(() => ({ opacity: 1 - pressed.value }));
const edgePressed = useAnimatedStyle(() => ({ opacity: pressed.value }));

// a 34pt button still has a 44pt target
const slop = hitSlop ?? Math.max(0, Math.ceil((hit.min - dims.height) / 2));
Expand Down Expand Up @@ -164,6 +167,18 @@ export const Button = ({
pressFill,
]}
/>
{variant === 'primary' && (
<>
<Animated.View
pointerEvents="none"
style={[styles.edge, styles.edgeRest, { borderRadius: dims.radius }, edgeRest]}
/>
<Animated.View
pointerEvents="none"
style={[styles.edge, styles.edgePressed, { borderRadius: dims.radius }, edgePressed]}
/>
</>
)}
{/* the label stays laid out (invisible) while loading, so the width does not jump */}
<View
style={[styles.content, loading ? styles.hidden : null]}
Expand Down Expand Up @@ -204,6 +219,9 @@ const styles = StyleSheet.create((theme) => ({
content: { flexShrink: 1, flexDirection: 'row', alignItems: 'center', gap: theme.space.xs },
hidden: { opacity: 0 },
bordered: { borderWidth: 2 },
edge: { ...StyleSheet.absoluteFillObject, borderColor: 'rgba(1,52,79,0.12)' },
edgeRest: { borderBottomWidth: 3 },
edgePressed: { borderBottomWidth: 1 },
label: { flexShrink: 1, textAlign: 'center' },
spinner: {
...StyleSheet.absoluteFillObject,
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/design/components/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const DateField = ({
>
<CalendarIcon size={20} color={palette.navy} strokeWidth={2} />
<AppText
numberOfLines={1}
numberOfLines={2}
testID={`${testID}-value`}
color={value && !disabled ? palette.navy : palette.ink2}
style={[fieldStyles.value, value ? null : fieldStyles.placeholder]}
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/design/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function Select<K extends SelectKey>({
]}
>
<AppText
numberOfLines={1}
numberOfLines={2}
color={selected ? (disabled ? palette.ink2 : palette.navy) : palette.ink2}
style={[fieldStyles.value, selected ? null : fieldStyles.placeholder]}
>
Expand Down
10 changes: 9 additions & 1 deletion mobile/src/design/components/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,15 @@ export const fieldStyles = StyleSheet.create((theme) => ({
},
inputMultiline: { minHeight: 60, textAlignVertical: 'top' },
inputDisabled: { color: theme.colors.textSecondary },
value: { flex: 1, fontFamily: theme.fontFamily.textBold, fontSize: 15.5, lineHeight: 21 },
// select / date value: wraps to a second line for long names, with room above and below
value: {
flex: 1,
minWidth: 0,
paddingVertical: 6,
fontFamily: theme.fontFamily.textBold,
fontSize: 15.5,
lineHeight: 21,
},
placeholder: { fontFamily: theme.fontFamily.text },
errorFold: { overflow: 'hidden' },
errorRow: {
Expand Down
7 changes: 5 additions & 2 deletions mobile/src/layouts/LayoutHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AppText } from '../design/components/Text';
import { springTo } from '../design/motion';
import { ChevronLeftIcon, CloseIcon, EditIcon } from '../design/icons';
import { hit, palette } from '../design/tokens';
import { displayTitle } from './displayTitle';

export interface RoundIconButtonProps {
icon: 'back' | 'close';
Expand Down Expand Up @@ -80,6 +81,7 @@ export const LayoutHeader = ({
}: LayoutHeaderProps) => (
<View style={[styles.header, style]}>
{leading}
{/* long titles („Completează profil voluntar”) wrap to a second line instead of being cut */}
<AppText
variant="title"
numberOfLines={2}
Expand Down Expand Up @@ -124,9 +126,10 @@ const styles = StyleSheet.create((theme) => ({
justifyContent: 'center',
},
roundDark: { backgroundColor: 'rgba(255,255,255,0.14)' },
title: { flex: 1, fontSize: 22, lineHeight: 26, letterSpacing: -0.5 },
titleAlone: { fontSize: 24, lineHeight: 28 },
title: { flex: 1, minWidth: 0, ...displayTitle(22, 1.1, -0.025) },
titleAlone: displayTitle(24, 1.1, -0.025),
edit: {
flexShrink: 0,
minHeight: hit.min,
flexDirection: 'row',
alignItems: 'center',
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/layouts/ModalLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default ModalLayout;
const styles = StyleSheet.create((theme) => ({
screen: { flex: 1, backgroundColor: theme.colors.background },
header: { paddingTop: theme.space.m },
childrenContainer: { flex: 1, paddingHorizontal: 16 },
childrenContainer: { flex: 1, paddingHorizontal: theme.space.gutter },
bottomActionContainer: {
width: '100%',
backgroundColor: theme.colors.background,
Expand Down
6 changes: 5 additions & 1 deletion mobile/src/layouts/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ export default PageLayout;

const styles = StyleSheet.create((theme) => ({
screen: { flex: 1, backgroundColor: theme.colors.background },
childrenContainer: { flex: 1, paddingHorizontal: 16, paddingVertical: theme.space.xs },
childrenContainer: {
flex: 1,
paddingHorizontal: theme.space.gutter,
paddingVertical: theme.space.xs,
},
actions: {
gap: theme.space.s,
paddingTop: theme.space.m,
Expand Down
26 changes: 26 additions & 0 deletions mobile/src/layouts/displayTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Bricolage Grotesque title metrics for the auth, onboarding and layout titles.
*
* The font's ascent (.93em) and descent (.27em) are tall, so with the tight line height of the
* handbook (≈ .96em for large titles) the accents of Î / Ș / Ă and the commas of ș / ț reach
* outside the text box: iOS lays the glyphs from the bottom of a short line (no baseline offset
* when the line is shorter than the font), Android clips at the view's edge. The box gets that
* headroom as padding, cancelled by negative margins, so the layout keeps the tight line height
* and nothing is clipped. Letter spacing is in points, computed for the size used.
*/
export const displayTitle = (size: number, ratio = 0.96, tracking = -0.04) => {
const top = Math.ceil(size * 0.18);
const bottom = Math.ceil(size * 0.1);
return {
fontSize: size,
lineHeight: Math.round(size * ratio),
letterSpacing: Math.round(size * tracking * 100) / 100,
includeFontPadding: false,
paddingTop: top,
marginTop: -top,
paddingBottom: bottom,
marginBottom: -bottom,
};
};

export default displayTitle;
6 changes: 3 additions & 3 deletions mobile/src/screens/CreateUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { InternalErrors } from '../common/errors/internal-errors.class';
import { CONSTANTS, REGEX } from '../common/constants/constants';
import { renderPhoneNumberPrefix } from '../components/InputPrefixes';
import { usePaddingTop } from '../hooks/usePaddingTop';
import { displayTitle } from '../layouts/displayTitle';

export type UserFormTypes = {
firstName: string;
Expand Down Expand Up @@ -151,7 +152,6 @@ const CreateUser = ({ navigation }: any) => {
headerStyle={{ paddingTop }}
>
<FormLayout scrollRef={scrollRef}>
{/* 20pt side margins: the layout gives 16 */}
<View style={styles.content}>
<Animated.View style={styles.intro} entering={enter(0, reduced)}>
<AppText variant="largeTitle" style={styles.heading} accessibilityRole="header">
Expand Down Expand Up @@ -275,9 +275,9 @@ const CreateUser = ({ navigation }: any) => {
export default CreateUser;

const styles = StyleSheet.create((theme) => ({
content: { marginHorizontal: theme.space.gutter - 16, gap: 18 },
content: { gap: 18 },
intro: { gap: theme.space.s, paddingTop: theme.space.xxs, paddingBottom: theme.space.xxs },
heading: { fontSize: 36, lineHeight: 38 },
heading: displayTitle(36),
// the consent sits on a white card so it reads as one block
terms: {
backgroundColor: theme.colors.card,
Expand Down
6 changes: 4 additions & 2 deletions mobile/src/screens/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { MailIcon } from '../design/icons';
import { palette } from '../design/tokens';
import { hasSeenOnboarding } from '../common/utils/onboarding';
import { isMigrationNoticeActive } from '../common/utils/migration-notice';
import { displayTitle } from '../layouts/displayTitle';

const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

Expand Down Expand Up @@ -251,8 +252,9 @@ const styles = StyleSheet.create((theme) => ({
content: { flexGrow: 1, paddingHorizontal: 24 },
copy: { gap: 12 },
copyCompact: { gap: 8 },
title: { fontSize: 46, lineHeight: 46, maxWidth: 340 },
titleCompact: { fontSize: 38, lineHeight: 39 },
// the masthead headline: tight like the handbook's h1, with headroom for Î / ț (see displayTitle)
title: { ...displayTitle(46, 0.92), maxWidth: 340 },
titleCompact: displayTitle(38, 0.94),
subtitle: {
color: 'rgba(255,255,255,0.78)',
fontFamily: theme.fontFamily.textBold,
Expand Down
Loading
Loading