Skip to main content

Overview

The Tafseer system consists of two main components:
  • TafseerPopup: A bottom sheet modal container
  • Tafseer: The content component displaying tafseer text with tab navigation

TafseerPopup Component

Props

show
boolean
required
Controls the visibility of the bottom sheet
setShow
React.Dispatch<React.SetStateAction<boolean>>
required
Function to update the visibility state
aya
number
required
The verse (Aya) number to display tafseer for
surah
number
required
The chapter (Surah) number

Implementation

TafseerPopup.tsx (lines 25-48)
export default function TafseerPopup({ show, setShow, aya, surah }: Props) {
  const colorScheme = useColorScheme();
  const tintColor = Colors[colorScheme ?? 'light'].tint;
  const backgroundColor = Colors[colorScheme ?? 'light'].background;
  const bottomSheetRef = useRef<BottomSheet>(null);
  const [opacity, setOpacity] = useState(1);
  const animatedPosition = useSharedValue(0);

  useAnimatedReaction(
    () => animatedPosition.value,
    (currentValue) => {
      const isResizing = currentValue % 1 !== 0;
      runOnJS(setOpacity)(isResizing ? 0.8 : 1);
    },
  );

  const snapPoints = useMemo(() => ['40%', '70%', '90%'], []);

  const handleSheetChange = useCallback(
    (index: number) => {
      if (index === -1) setShow(false);
    },
    [setShow],
  );
}

Snap Points

The bottom sheet has three snap points:
TafseerPopup.tsx (line 41)
const snapPoints = useMemo(() => ['40%', '70%', '90%'], []);
Initial view - Good for quick reference

Resize Animation

The component provides visual feedback during resize:
TafseerPopup.tsx (lines 33-39)
useAnimatedReaction(
  () => animatedPosition.value,
  (currentValue) => {
    const isResizing = currentValue % 1 !== 0;
    runOnJS(setOpacity)(isResizing ? 0.8 : 1);
  },
);
During resize (when position is not a whole number), opacity is reduced to 0.8 for better UX.

Backdrop Configuration

TafseerPopup.tsx (lines 50-61)
const renderBackdrop = useCallback(
  (props: any) => (
    <BottomSheetBackdrop
      {...props}
      disappearsOnIndex={-1}
      appearsOnIndex={0}
      opacity={0.6}
      onPress={() => bottomSheetRef.current?.close()}
    />
  ),
  [],
);

Custom Handle

TafseerPopup.tsx (lines 63-72)
const renderHandle = useCallback(
  () => (
    <ThemedView style={styles.resizer}>
      <ThemedView
        style={[styles.resizerIcon, { backgroundColor: tintColor }]}
      />
    </ThemedView>
  ),
  [tintColor],
);

BottomSheet Render

TafseerPopup.tsx (lines 76-100)
<BottomSheet
  ref={bottomSheetRef}
  index={1}
  snapPoints={snapPoints}
  onChange={handleSheetChange}
  backdropComponent={renderBackdrop}
  handleComponent={renderHandle}
  enablePanDownToClose
  backgroundStyle={{ backgroundColor }}
  animatedPosition={animatedPosition}
  activeOffsetY={[-1, 1]}
>
  <BottomSheetScrollView
    contentContainerStyle={styles.content}
    showsVerticalScrollIndicator={false}
  >
    <Suspense
      fallback={<ActivityIndicator size="large" color={tintColor} />}
    >
      <Tafseer aya={aya} surah={surah} opacity={opacity} />
    </Suspense>
  </BottomSheetScrollView>
</BottomSheet>

Tafseer Component

Props

aya
number
required
The verse number to display tafseer for
surah
number
required
The surah (chapter) number
opacity
number
Optional opacity value for the content (default: undefined)

Tafseer Sources

The component supports 11 different tafseer sources:
Tafseer.tsx (lines 19-31)
const tabLabels: Partial<Record<TafseerTabs, string>> = {
  katheer: 'إبن كثير',
  maany: 'معاني القرآن',
  earab: 'إعراب القرآن',
  baghawy: 'البغوي',
  muyassar: 'الميسر',
  qortoby: 'القرطبي',
  tabary: 'الطبري',
  saady: 'السعدي',
  wahidy: 'أسباب النزول',
  tanweer: 'التحرير و التنوير',
  waseet: 'الوسيط',
};
Classical tafseer by Ismail ibn Katheer - One of the most respected commentaries

State Management

Tafseer.tsx (lines 39-54)
export default function Tafseer({ aya, surah, opacity = undefined }: Props) {
  const { tintColor, textColor } = useColors();
  const { surahData } = useQuranMetadata();
  const [surahName, setSurahName] = useState<string>('');
  const [selectedTabValue, setSelectedTab] = useAtom(tafseerTab);
  const [tafseerData, setTafseerData] = useState<TafseerAya[] | null>(null);
  const [tabsWithContent, setTabsWithContent] = useState<
    Record<TafseerTabs, boolean>
  >({} as Record<TafseerTabs, boolean>);

  const { specsData } = useQuranMetadata();

  const { countBesmalAya } = specsData;

  const formattedTafseerHtml = useTafseerContent({ tafseerData, surah, aya });
  const isCurrentTabEmpty = hasNoTafseerContent({ tafseerData, surah, aya });
}

Dynamic Content Loading

Tafseer content is loaded dynamically based on the selected tab:
Tafseer.tsx (lines 151-200)
const loadTafseerData = useCallback(async () => {
  let tafseerJSON;
  try {
    switch (selectedTabValue) {
      case 'baghawy':
        tafseerJSON = await import('@/assets/tafaseer/baghawy.json');
        break;
      case 'earab':
        tafseerJSON = await import('@/assets/tafaseer/earab.json');
        break;
      case 'katheer':
        tafseerJSON = await import('@/assets/tafaseer/katheer.json');
        break;
      case 'maany':
        tafseerJSON = await import('@/assets/tafaseer/maany.json');
        break;
      case 'muyassar':
        tafseerJSON = await import('@/assets/tafaseer/muyassar.json');
        break;
      case 'qortoby':
        tafseerJSON = await import('@/assets/tafaseer/qortoby.json');
        break;
      case 'saady':
        tafseerJSON = await import('@/assets/tafaseer/saady.json');
        break;
      case 'tabary':
        tafseerJSON = await import('@/assets/tafaseer/tabary.json');
        break;
      case 'wahidy':
        tafseerJSON = await import('@/assets/tafaseer/nozool-wahidy.json');
        break;
      case 'tanweer':
        tafseerJSON = await import('@/assets/tafaseer/tanweer.json');
        break;
      case 'waseet':
        tafseerJSON = await import('@/assets/tafaseer/waseet.json');
        break;
      default:
        tafseerJSON = await import('@/assets/tafaseer/katheer.json');
    }
    setTafseerData(
      (tafseerJSON.default as TafseerAya[]) || (tafseerJSON as TafseerAya[]),
    );
  } catch {
    setTafseerData(null);
  }
}, [selectedTabValue]);
Dynamic imports reduce initial bundle size and improve performance by loading only the selected tafseer.

Content Availability Detection

The component preloads all tafseer sources to detect which ones have content for the current verse:
Tafseer.tsx (lines 71-149)
useEffect(() => {
  if (!allTafseersLoaded) {
    const loadAllTafseers = async () => {
      const tabsToCheck = Object.keys(tabLabels) as TafseerTabs[];

      for (const tab of tabsToCheck) {
        try {
          let tafseerJSON;
          // Import logic for each tafseer source...
          
          const data =
            (tafseerJSON?.default as TafseerAya[]) ||
            (tafseerJSON as TafseerAya[]);
          const hasContent = !hasNoTafseerContent({
            tafseerData: data,
            surah,
            aya,
          });

          setTabsWithContent((prev) => ({
            ...prev,
            [tab]: hasContent,
          }));
        } catch {
          setTabsWithContent((prev) => ({
            ...prev,
            [tab]: false,
          }));
        }
      }

      setAllTafseersLoaded(true);
    };

    loadAllTafseers();
  }
}, [surah, aya, allTafseersLoaded]);

Tab Navigation

Tafseer.tsx (lines 213-253)
<ThemedView style={[styles.tabs, { backgroundColor: 'transparent' }]}>
  {Object.keys(tabLabels).map((key) => {
    const tabKey = key as TafseerTabs;

    const isCurrentTab = tabKey === selectedTabValue;
    const hasNoContent =
      tabsWithContent[tabKey] === false ||
      (isCurrentTab &&
        isCurrentTabEmpty &&
        tabsWithContent[tabKey] === undefined);

    return (
      <Pressable
        key={tabKey}
        style={[
          styles.tabButton,
          selectedTabValue === tabKey && styles.activeTab,
          selectedTabValue === tabKey && {
            borderColor: tintColor,
          },
          hasNoContent && styles.disabledTab,
          { backgroundColor: 'transparent' },
        ]}
        onPress={() => setSelectedTab(tabKey)}
        accessibilityLabel={`${tabLabels[tabKey]} tab for Surah ${surahName}, Aya ${aya}`}
        accessibilityHint={`Tap to see the tafseer for Surah ${surahName}, Aya ${aya} from ${tabLabels[tabKey]}`}
      >
        <ThemedText
          style={[
            { color: tintColor, backgroundColor: 'transparent' },
            hasNoContent && styles.disabledTabText,
          ]}
        >
          {tabLabels[tabKey]}
        </ThemedText>
      </Pressable>
    );
  })}
</ThemedView>
Tabs without content for the current verse are displayed with reduced opacity and strikethrough text.

HTML Rendering

Tafseer content is rendered as HTML with custom styling:
Tafseer.tsx (lines 255-284)
{tafseerData ? (
  <ThemedView style={{ flex: 1 }}>
    {/* @ts-ignore - Ignoring type error for HTMLView component */}
    <HTMLView
      value={formattedTafseerHtml}
      style={{
        paddingHorizontal: 10,
        paddingVertical: 5,
        backgroundColor: 'transparent',
      }}
      stylesheet={{
        div: {
          color: textColor,
          fontFamily: 'Tajawal_400Regular',
          fontSize: 16,
          lineHeight: 24,
          backgroundColor: 'transparent',
        },
        p: {
          color: textColor,
          fontFamily: 'Tajawal_400Regular',
          fontSize: 16,
          lineHeight: 24,
          flexDirection: 'row',
          backgroundColor: 'transparent',
        },
      }}
      addLineBreaks={false}
    />
  </ThemedView>
) : (
  <ActivityIndicator size="large" color={tintColor} />
)}

TypeScript Interfaces

TafseerPopup Props

TafseerPopup.tsx (lines 18-23)
type Props = {
  show: boolean;
  setShow: React.Dispatch<React.SetStateAction<boolean>>;
  aya: number;
  surah: number;
};

Tafseer Props

Tafseer.tsx (lines 33-37)
type Props = {
  aya: number;
  surah: number;
  opacity?: number | undefined;
};

Styling

TafseerPopup Styles

TafseerPopup.tsx (lines 103-119)
const styles = StyleSheet.create({
  content: {
    flexGrow: 1,
    paddingHorizontal: 2,
    paddingBottom: 2,
  },
  resizer: {
    alignSelf: 'center',
    paddingVertical: 10,
  },
  resizerIcon: {
    width: 80,
    height: 3,
    borderRadius: 3,
    alignSelf: 'center',
  },
});

Tafseer Styles

Tafseer.tsx (lines 292-326)
const styles = StyleSheet.create({
  container: {
    flex: 1,
    height: '100%',
    backgroundColor: 'transparent',
    padding: 5,
  },
  title: {
    fontSize: 18,
    marginBottom: 10,
    padding: 8,
    fontFamily: 'Amiri_400Regular',
  },
  tabs: {
    flexDirection: 'row',
    alignItems: 'center',
    flexWrap: 'wrap',
    gap: 2,
    justifyContent: 'flex-start',
    marginBottom: 10,
  },
  tabButton: {
    paddingVertical: 10,
    paddingHorizontal: 15,
  },
  activeTab: {
    borderBottomWidth: 2,
  },
  disabledTab: {
    opacity: 0.5,
  },
  disabledTabText: {
    textDecorationLine: 'line-through',
  },
});
  • PageOverlay: Triggers the TafseerPopup when verses are tapped
  • ThemedView/ThemedText: Provide theme-aware styling

Build docs developers (and LLMs) love