infra: add linting and formatting for js projects (#230)
* wip * wip * wip * format * wip(mobile): lint and format * cleanup * nits * nits * idiomatic react * nit * format all root files
This commit was merged in pull request #230.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { Text, View } from "react-native";
|
||||
import type { Human } from "@/api/types";
|
||||
import { resolveHumanDisplay } from "@/lib/humans";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Text, View } from 'react-native';
|
||||
import type { Human } from '@/api/types';
|
||||
import { resolveHumanDisplay } from '@/lib/humans';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type Size = "xs" | "sm" | "md";
|
||||
type Size = 'xs' | 'sm' | 'md';
|
||||
|
||||
interface AvatarProps {
|
||||
humanId: string | null | undefined;
|
||||
@@ -17,9 +17,9 @@ interface AvatarProps {
|
||||
}
|
||||
|
||||
const sizeMap: Record<Size, { box: string; text: string; ring: number }> = {
|
||||
xs: { box: "h-6 w-6", text: "text-[9px]", ring: 1.5 },
|
||||
sm: { box: "h-9 w-9", text: "text-xs", ring: 2 },
|
||||
md: { box: "h-10 w-10", text: "text-sm", ring: 2 },
|
||||
xs: { box: 'h-6 w-6', text: 'text-[9px]', ring: 1.5 },
|
||||
sm: { box: 'h-9 w-9', text: 'text-xs', ring: 2 },
|
||||
md: { box: 'h-10 w-10', text: 'text-sm', ring: 2 },
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ const sizeMap: Record<Size, { box: string; text: string; ring: number }> = {
|
||||
export function Avatar({
|
||||
humanId,
|
||||
humans,
|
||||
size = "sm",
|
||||
size = 'sm',
|
||||
online = false,
|
||||
stackBg,
|
||||
className,
|
||||
@@ -41,7 +41,7 @@ export function Avatar({
|
||||
return (
|
||||
<View
|
||||
className={cn(
|
||||
"bg-black/15 items-center justify-center rounded-full",
|
||||
'bg-black/15 items-center justify-center rounded-full',
|
||||
dims.box,
|
||||
className,
|
||||
)}
|
||||
@@ -49,10 +49,10 @@ export function Avatar({
|
||||
// Online ring is the priority; if not online, show the stack
|
||||
// separator ring (if requested) so adjacent avatars stay distinct.
|
||||
borderWidth: online ? dims.ring : stackBg ? dims.ring : 0,
|
||||
borderColor: online ? "#22c55e" : stackBg ?? "transparent",
|
||||
borderColor: online ? '#22c55e' : (stackBg ?? 'transparent'),
|
||||
}}
|
||||
>
|
||||
<Text className={cn("text-white font-semibold", dims.text)}>
|
||||
<Text className={cn('text-white font-semibold', dims.text)}>
|
||||
{initials}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
KeyboardAvoidingView,
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
Platform,
|
||||
Pressable,
|
||||
View,
|
||||
} from "react-native";
|
||||
} from 'react-native';
|
||||
import {
|
||||
initialWindowMetrics,
|
||||
SafeAreaProvider,
|
||||
SafeAreaView,
|
||||
} from "react-native-safe-area-context";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
} from 'react-native-safe-area-context';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import Animated, {
|
||||
Easing,
|
||||
Extrapolation,
|
||||
@@ -22,9 +22,9 @@ import Animated, {
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const SCREEN_HEIGHT = Dimensions.get("window").height;
|
||||
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
const ANIMATION_MS = 240;
|
||||
|
||||
interface BottomSheetProps {
|
||||
@@ -58,7 +58,7 @@ export function BottomSheet({
|
||||
onClose,
|
||||
onClosed,
|
||||
avoidKeyboard = false,
|
||||
maxHeight = "85%",
|
||||
maxHeight = '85%',
|
||||
children,
|
||||
}: BottomSheetProps) {
|
||||
// Mount slightly past `open` so the slide-in animation has its starting
|
||||
@@ -66,6 +66,9 @@ export function BottomSheet({
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const translateY = useSharedValue(SCREEN_HEIGHT);
|
||||
|
||||
// Mount as soon as we open; the close path unmounts after the exit animation.
|
||||
if (open && !mounted) setMounted(true);
|
||||
|
||||
// Latest onClosed in a ref so the worklet→JS bridge always invokes the
|
||||
// current callback even if the parent re-rendered with a new closure.
|
||||
const onClosedRef = useRef(onClosed);
|
||||
@@ -80,7 +83,6 @@ export function BottomSheet({
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setMounted(true);
|
||||
requestAnimationFrame(() => {
|
||||
translateY.value = withSpring(0, {
|
||||
damping: 24,
|
||||
@@ -104,14 +106,18 @@ export function BottomSheet({
|
||||
.activeOffsetY(10)
|
||||
.failOffsetX([-25, 25])
|
||||
.onUpdate((e) => {
|
||||
"worklet";
|
||||
'worklet';
|
||||
// Reanimated shared values are mutated by design; react-hooks/immutability
|
||||
// doesn't model worklets, so the mutations below are flagged spuriously.
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
translateY.value = Math.max(0, e.translationY);
|
||||
})
|
||||
.onEnd((e) => {
|
||||
"worklet";
|
||||
'worklet';
|
||||
if (e.translationY > 120 || e.velocityY > 800) {
|
||||
runOnJS(onClose)();
|
||||
} else {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
translateY.value = withSpring(0, {
|
||||
damping: 24,
|
||||
stiffness: 260,
|
||||
@@ -138,7 +144,7 @@ export function BottomSheet({
|
||||
|
||||
const Wrapper = avoidKeyboard ? KeyboardAvoidingView : View;
|
||||
const wrapperProps = avoidKeyboard
|
||||
? { behavior: Platform.OS === "ios" ? ("padding" as const) : undefined }
|
||||
? { behavior: Platform.OS === 'ios' ? ('padding' as const) : undefined }
|
||||
: {};
|
||||
|
||||
return (
|
||||
@@ -151,9 +157,9 @@ export function BottomSheet({
|
||||
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Animated.View
|
||||
pointerEvents={open ? "auto" : "none"}
|
||||
pointerEvents={open ? 'auto' : 'none'}
|
||||
style={[
|
||||
{ position: "absolute", inset: 0, backgroundColor: "black" },
|
||||
{ position: 'absolute', inset: 0, backgroundColor: 'black' },
|
||||
backdropStyle,
|
||||
]}
|
||||
>
|
||||
@@ -162,23 +168,23 @@ export function BottomSheet({
|
||||
|
||||
<Wrapper
|
||||
{...wrapperProps}
|
||||
style={{ flex: 1, justifyContent: "flex-end" }}
|
||||
style={{ flex: 1, justifyContent: 'flex-end' }}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<GestureDetector gesture={sheetPan}>
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
backgroundColor: "#1c1c1c",
|
||||
backgroundColor: '#1c1c1c',
|
||||
borderTopLeftRadius: 22,
|
||||
borderTopRightRadius: 22,
|
||||
overflow: "hidden",
|
||||
overflow: 'hidden',
|
||||
maxHeight,
|
||||
},
|
||||
sheetStyle,
|
||||
]}
|
||||
>
|
||||
<SafeAreaView edges={["bottom"]}>
|
||||
<SafeAreaView edges={['bottom']}>
|
||||
<View className="px-5 pt-3 items-center">
|
||||
<View className="bg-white/25 h-1 w-12 rounded-full mb-3" />
|
||||
</View>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { useEffect } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withRepeat,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import type { Human } from "@/api/types";
|
||||
import { resolveHumanDisplay } from "@/lib/humans";
|
||||
import type { ComposingUser } from "@/features/stream-view/stream-presence-context";
|
||||
} from 'react-native-reanimated';
|
||||
import type { Human } from '@/api/types';
|
||||
import { resolveHumanDisplay } from '@/lib/humans';
|
||||
import type { ComposingUser } from '@/features/stream-view/stream-presence-context';
|
||||
|
||||
interface ComposingIndicatorProps {
|
||||
users: ComposingUser[];
|
||||
@@ -29,13 +29,16 @@ export function ComposingIndicator({
|
||||
if (users.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View pointerEvents="none" className="flex-row flex-wrap items-center gap-1.5">
|
||||
<View
|
||||
pointerEvents="none"
|
||||
className="flex-row flex-wrap items-center gap-1.5"
|
||||
>
|
||||
{users.map((u) => {
|
||||
const { displayName } = resolveHumanDisplay(u.humanId, networkHumans);
|
||||
const label =
|
||||
u.mode === "recording"
|
||||
u.mode === 'recording'
|
||||
? `${displayName} is recording`
|
||||
: u.mode === "screen"
|
||||
: u.mode === 'screen'
|
||||
? `${displayName} is sharing`
|
||||
: `${displayName} is typing`;
|
||||
|
||||
@@ -87,9 +90,6 @@ function Dot({ delay }: { delay: number }) {
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
className="bg-white/85 h-1 w-1 rounded-full"
|
||||
style={style}
|
||||
/>
|
||||
<Animated.View className="bg-white/85 h-1 w-1 rounded-full" style={style} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
type Props = {
|
||||
height?: number;
|
||||
@@ -7,7 +7,7 @@ type Props = {
|
||||
|
||||
const ASPECT_RATIO = 89 / 18;
|
||||
|
||||
export function FlowyLogo({ height = 18, color = "#828282" }: Props) {
|
||||
export function FlowyLogo({ height = 18, color = '#828282' }: Props) {
|
||||
const width = height * ASPECT_RATIO;
|
||||
return (
|
||||
<Svg width={width} height={height} viewBox="0 0 89 18" fill="none">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { View } from "react-native";
|
||||
import { View } from 'react-native';
|
||||
|
||||
/**
|
||||
* Inset hairline separator for edge-to-edge list rows. Pass to a FlatList
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text, type TextProps } from "react-native";
|
||||
import { formatDistanceToNow } from "@/lib/time-utils";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text, type TextProps } from 'react-native';
|
||||
import { formatDistanceToNow } from '@/lib/time-utils';
|
||||
|
||||
const MINUTE_MS = 60_000;
|
||||
|
||||
interface RelativeTimestampProps extends Omit<TextProps, "children"> {
|
||||
interface RelativeTimestampProps extends Omit<TextProps, 'children'> {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user