Eight New Primitives in rn-markdown-editor — Layout, Typography, and Utility Components

in Steem Dev16 days ago

Previously:


Version v1.0.23 is now live on npm. This release shifts focus from interactive components to the foundational layer underneath them — layout primitives, typography, form controls, and utility components that reduce repetition across your entire app. Eight new additions shipped in this version. Here's a detailed breakdown of each one.


Checkmark

A standalone checkbox indicator component — not a full form field, but the visual primitive that a checkbox field is built from. This separation matters: you can drop Checkmark into a list row, a permission toggle, or a custom form control without coupling it to any specific input management pattern.

How it works internally:

The component supports three visual states: unchecked, checked, and indeterminate. Both the check glyph and the indeterminate dash are rendered as inline SVG via react-native-svg, sized proportionally at size * 0.6 and size * 0.55 respectively so the glyph scales cleanly at any dimension without pixel artifacts.

All color and geometry computations — background, border color, border width, border radius — are consolidated inside a single useMemo block that depends only on the props and theme colors. This means a single dependency array covers all the visual math, and the container style is derived from that result in a second useMemo, so neither recalculates unless something relevant actually changes.

The active state collapses the border (borderWidth: 0) and fills the background, while the inactive state shows only the border ring with a transparent fill. Disabled state replaces the active color with colors.muted in both the background and border, giving a clear visual signal without hiding the component.

Accessibility is handled natively: accessibilityRole="checkbox" and accessibilityState={{ checked, disabled }} are set directly, with indeterminate mapped to the "mixed" checked value that screen readers expect.

// Unchecked
<Checkmark checked={false} size={20} />

// Checked
<Checkmark checked={true} size={20} />

// Indeterminate (e.g., "select all" with partial selection)
<Checkmark indeterminate={true} size={20} />

// Disabled
<Checkmark checked={true} disabled size={20} />

Props:

PropTypeDefaultDescription
checkedbooleanfalseChecked state
indeterminatebooleanfalseRenders dash glyph, overrides checked
disabledbooleanfalseApplies muted color, removes interaction
sizenumber20Width and height in pixels
radiusnumbersize * 0.25Border radius override
activeColorstringcolors.foregroundFill color when active
borderColorstringcolors.borderBorder color when inactive
glyphColorstringcolors.backgroundCheck/dash glyph color
disabledColorstringcolors.mutedColor applied in disabled state

Circle

A layout primitive that renders a circular View. Small in scope, but something you end up writing by hand in a dozen places — notification badges, avatar rings, status dots, icon containers.

How it works internally:

The radius is derived from Math.max(width, height) / 2, which means non-square dimensions still produce a pill shape rather than an oval. The style object is memoized and merged with any passed style prop. All standard ViewProps pass through, so you can attach onPress, testID, or any other native prop directly.

// Status dot
<Circle width={10} height={10} bg={colors.success} />

// Icon container
<Circle width={40} height={40} bg={colors.secondary}>
  <Icons.Star size={20} color={colors.foreground} />
</Circle>

Props:

PropTypeDefaultDescription
widthnumber16Width in pixels
heightnumber16Height in pixels
bgstring"#fff"Background color
styleViewStyleAdditional style overrides

Container

A centered, max-width layout wrapper driven by named size tokens from the active theme. Stops you from hardcoding breakpoint widths in individual screens and keeps layout decisions in one place.

How it works internally:

Container reads container.containerSizes from useTheme() and looks up the value for the size prop key. The resolved maxWidth is applied alongside width: "100%" and alignSelf: "center", so the container stays full-width on small screens and caps out at the token value on wider ones. Changing the theme's container token map updates every Container instance at once.

<Container size="md">
  <Heading type="h1">Page Title</Heading>
  <Text>Content constrained to the md breakpoint max-width.</Text>
</Container>

Props:

PropTypeDescription
size`"xs" \"sm" \"md" \"lg" \"xl" \"2xl"`Named width token
styleViewStyleAdditional style overrides

Flex

A single-line horizontal row with a built-in gap prop. Eliminates the pattern of writing flexDirection: "row" and a gap value on a plain View every time you need to lay items out horizontally.

How it works internally:

The style is a useMemo over gap (defaulting to 8) merged with any passed style. Everything else passes through as standard ViewProps. The implementation is intentionally minimal — Flex is a convenience wrapper, not a full flexbox abstraction.

<Flex gap={12}>
  <Avatar name="Suraj Adhikari" size={32} />
  <Heading type="h4">Suraj Adhikari</Heading>
</Flex>

<Flex gap={4}>
  <Icons.Heart size={16} color={colors.destructive} />
  <Text>142 likes</Text>
</Flex>

Props:

PropTypeDefaultDescription
gapnumber8Horizontal gap between children
styleViewStyleAdditional style overrides

Float

A floating badge component — a small labeled circle absolutely positioned at the top-right of its parent. The canonical use case is a notification count on a tab icon or avatar.

How it works internally:

Float composes Circle with an absolute position style (top: 0, right: 0) and renders a Text child inside it. The circle background defaults to colors.destructive (red) but accepts a background override. Text color defaults to colors.foreground and accepts a textColor override. Font size is fixed at 10 to stay readable inside small badge circles.

Since Float is meant to be used inside a View with position: "relative", no additional wrapper is needed on the parent — just make sure the parent has a defined size.

<View style={{ position: "relative" }}>
  <Icons.Bell size={24} color={colors.foreground} />
  <Float height={16} width={16}>3</Float>
</View>

// Custom color
<Float height={18} width={18} background={colors.primary} textColor="#fff">
  9+
</Float>

Props:

PropTypeDefaultDescription
heightnumberBadge height
widthnumberBadge width
backgroundstringcolors.destructiveBadge background color
textColorstringcolors.foregroundLabel text color
textStyleTextPropsAdditional text style

For

A declarative list-rendering component — JSX-native .map() with a typed render callback and a built-in empty state fallback. Replaces the pattern of inline array.map(...) calls that litter render functions with inconsistent key handling.

How it works internally:

For is generic over T, so the children render callback is fully typed based on whatever array you pass to each. The empty/undefined guard runs before the map, returning fallback ?? null when each is empty. The component is wrapped with React.memo via a cast that preserves the generic signature — React.memo strips generics by default, so the cast is necessary to keep the callback typed correctly at the call site.

// Primitive array
<For each={["Reply", "Repost", "Upvote"]}>
  {(action, index) => <Button key={index} label={action} />}
</For>

// Object array — item is fully typed
<For
  each={posts}
  fallback={<Text>No posts yet.</Text>}
>
  {(post, index) => (
    <Card key={post.id}>
      <CardHeader>
        <CardTitle>{post.title}</CardTitle>
      </CardHeader>
    </Card>
  )}
</For>

// Nested iteration
<For each={users}>
  {(user, i) => (
    <View key={i}>
      <Text>{user.name}</Text>
      <For each={user.tags}>
        {(tag, j) => <Badge key={j} label={tag} />}
      </For>
    </View>
  )}
</For>

Props:

PropTypeDescription
eachT[]Array to iterate over
children(item: T, index: number) => ReactNodeTyped render callback
fallbackReactNodeRendered when array is empty

Heading

A typed typography component for semantic headings — h1 through h5 — where font size and weight are resolved from the theme's typography token map rather than being hardcoded at the call site.

How it works internally:

Heading reads typography.headingSizes[type] from useTheme() and falls back to h6 sizes if the type isn't found, making it safe even if the token map is partially defined. Font color comes from colors.foreground. Both the heading style lookup and the final merged TextStyle are memoized, so re-renders that don't change type, style, or theme tokens are cheap. All standard TextProps pass through.

<Heading type="h1">Welcome Back</Heading>
<Heading type="h2">Recent Posts</Heading>
<Heading type="h4" style={{ marginBottom: 4 }}>
  Section Title
</Heading>

Props:

PropTypeDescription
type`"h1" \"h2" \"h3" \"h4" \"h5"`Heading level, resolves size and weight from theme
styleTextStyleAdditional style overrides

Highlight

A text component that highlights matched substrings within a string, useful for search results, mention rendering, and keyword callouts.

How it works internally:

The component accepts a query array of strings and builds a single regex from them — escaped, joined with |, and optionally wrapped in word boundary markers (\b) when exactMatch is true. The regex is memoized on query, ignoreCases, and exactMatch so it isn't rebuilt on every render.

The string is split on the regex, which preserves the matched parts as separate array entries (because the regex uses a capture group). Each part is classified as a match or not by checking against a lowercased query array when ignoreCases is on. Matched parts render as HighlightSpan (a Text with backgroundColor and borderRadius: 4); unmatched parts render as plain PlainSpan. Both inner components are memoized independently so re-renders caused by parent state changes don't cascade through the full part list.

// Search result highlighting
<Highlight
  query={["markdown", "editor"]}
  highlightColor={colors.flameon}
  ignoreCases
>
  The rn-markdown-editor is a fully themeable markdown editor for React Native.
</Highlight>

// Exact word match only
<Highlight
  query={["React"]}
  highlightColor="#FFD700"
  exactMatch
  ignoreCases={false}
>
  React Native differs from ReactDOM in its rendering target.
</Highlight>

Props:

PropTypeDefaultDescription
querystring[]Terms to highlight
highlightColorstringBackground color for matched spans
ignoreCasesbooleantrueCase-insensitive matching
exactMatchbooleanfalseWord boundary matching
highlightStyleTextStyleAdditional style for matched spans

Installation

All eight components are available now in v1.0.23:

npm install rn-markdown-editor

or

yarn add rn-markdown-editor

More components are in active development and will be covered in the next release post as soon as they land.


System Reliability & Support

The development team remains committed to maintaining a secure, efficient, and highly optimized open-source toolkit. Continuous updates are actively deployed to ensure total cross-platform stability across iOS, Android, and Web deployments.

For technical assistance or to report issues, please submit on the comments or our official NPM Project Page.

Thanks for your time and support. Let's make Steem great, again.


Official NPM Link: https://www.npmjs.com/package/rn-markdown-editor
Primary Architecture: TypeScript / JavaScript
Supported Environments: Expo & Bare React Native CLI
Utility Exports: useDebouncedInput, useDisclosure, useMergeState